AsHciADArtifactsPreCreationTool.psm1

<#############################################################
 # #
 # Copyright (C) Microsoft Corporation. All rights reserved. #
 # #
 #############################################################>


# Script to create AD objects from domain admin context prior to azurestack hci deployment driver execution.
#
# Requirements:
# - Ensure that the following parameters must be unquie in AD per cluster instance
# - Azurestack hci deployment user.
# - Azurestack hci organization unit name.
# - Azurestack hci deployment prefix (must be at the max 8 characters).
# - Azurestack hci deployment cluster name.
# - Physical nodes objects (if already created) must be available under hci ou computers organization unit.
#
# Objects to be created:
# - Hci Organizational Unit that will be the parent to an organizational unit for each instance and 2 sub organizational units (Computers and Users) with group policy inheritance blocked.
# - A user account (under users OU) that will have full control to that organizational unit.
# - Computer objects (if provided under Computers OU else all computer objects must be present under Computer OU)
# - Security groups (under users OU)
# - Group managed service accounts (gMSA) (under users OU)
# - Azurestack Hci cluster object (under computers OU)
#
# This script should be run by a user that has domain admin privileges to the domain.
#
# Parameter Ex:
# -AsHciOUName "OU=Hci001,OU=HciDeployments,DC=v,DC=masd,DC=stbtest,DC=microsoft,DC=com" [Hci001 OU will be created under OU=HciDeployments,DC=v,DC=masd,DC=stbtest,DC=microsoft,DC=com]
# -DomainFQDN "v.masd.stbtest.microsoft.com"
# -AsHciClusterName "s-cluster"
#
# Usage Ex:
#
# Hci cluster Deployment
#
# New-HciAdObjectsPreCreation -Deploy -AsHciDeploymentUserCredential (get-credential) -AsHciOUName "OU=Hci001,DC=v,DC=masd,DC=stbtest,DC=microsoft,DC=com" -AsHciPhysicalNodeList @("Physical Machine1", "Physical Machine2") -AsHciDeploymentPrefix "Hci001" -DomainFQDN "v.masd.stbtest.microsoft.com" -AsHciClusterName "s-cluster"
#
#
# Hci cluster Upgrade
# New-HciAdObjectsPreCreation -Upgrade -AsHciDeploymentUserCredential (get-credential) -AsHciOUName "OU=Hci001,DC=v,DC=masd,DC=stbtest,DC=microsoft,DC=com" -AsHciDeploymentPrefix "Hci001" -DomainFQDN "v.masd.stbtest.microsoft.com"
#


$ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop

<#
 .Synopsis
  Tests kds root key configuration.
 
 .Parameter DomainFQDN
  The AD domain fqdn.
#>


function Test-KDSConfiguration
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $DomainFQDN
    )

    $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop
    try
    {
        $adDomainController = Get-ADDomainController -DomainName $DomainFQDN -Discover -MinimumDirectoryServiceVersion Windows2012 -NextClosestSite
        $dcName = "$($adDomainController.Name).$($adDomainController.Domain)"
        Write-Verbose "Domain controller :: $dcName"
    }
    catch
    {
        Write-Error "Make sure a domain controller with minimum OS level of 'Windows Server 2012' is available"
    }

    # Checking for kds root key
    try
    {
        Write-Verbose "Checking for KDS root key"
        $KdsRootKeys = Get-KdsRootKey
        if ((-not $kdsRootKeys) -or ($kdsRootKeys.Count -eq 0))
        {
            Write-Error "KDS rootkey is not found/configured, please configure KDS root key. KDS rootkey Configure cmdlet 'Add-KdsRootKey '"
        }
        $kdsRootKeyEffective = $false

        foreach ($KdsRootKey in $KdsRootKeys)
        {
            # make sure it is effective at least 10 hours ago
            if(((Get-Date) - $KdsRootKey.EffectiveTime).TotalHours -ge 10)
            {
                Write-Verbose "Found KDS rootkey and it is effective "
                $kdsRootKeyEffective = $true
                break
            }
        }
        if (! $kdsRootKeyEffective)
        {
            Write-Error "Found KDS rootkey but it is not active."
        }
    }
    catch
    {
        Write-Error "Unable to verify KDS configuration. Error :: $_"
    }
}


<#
 .Synopsis
  Tests hci organization unit.
 
 .Parameter AsHciOUPath
  The hci ou path.
#>


function Test-AsHciOU
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $AsHciOUPath
    )

    $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop

    try
    {
        if (-not [adsi]::Exists("LDAP://$AsHciOUPath"))
        {
            Write-Error "'$AsHciOUPath' does not exist. Exception :: $_"
        }
        Write-Verbose "Successfully verified $AsHciOUPath"
    }
    catch
    {
        Write-Error "Unable to verify organization unit '$AsHciOUPath'. Exception :: $_"
    }
}

<#
 .Synopsis
  Verifies deployment prefix uniqueness.
 
 .Parameter AsHciOUPath
  The hci ou path.
 
 .Parameter AsHciDeploymentPrefix
  The hci deployment prefix.
 
#>


function Test-AsHciDeploymentPrefix
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $AsHciOUPath,

        [Parameter(Mandatory = $true)]
        [string]
        $AsHciDeploymentPrefix
    )

    $ErrorActionPreference = [System.Management.Automation.ActionPreference]::continue 
    $opsAdminGroup = "$AsHciDeploymentPrefix-OpsAdmin"
    $hciGroup = Get-AdGroup -Filter { Name -eq $opsAdminGroup}
    # Test for unique AsHciDeploymentPrefix
    if ($hciGroup)
    {
        if (-not ($hciGroup.DistinguishedName -match $AsHciOUPath) )
        {
            Write-Error "Deployment prefix '$AsHciDeploymentPrefix' is in use, please provide an unique prefix"
        }
    }
}

<#
 .Synopsis
  Creates HCI organization unit in a given AD domain.
 
 .Parameter DomainOUPath
  The AD domain organization unit path
 
 .Parameter HciOUName
  The HCI organizational unit name.
#>


function New-AsHciOrganizationalUnit
{
    [CmdletBinding(SupportsShouldProcess=$true)]
    Param
    (
        [Parameter(Mandatory = $true)]
        [string] 
        $DomainOUPath,

        [Parameter(Mandatory = $true)]
        [String] 
        $OUName,

        [Parameter(Mandatory = $false)]
        [bool] 
        $Rerun =$false
    )

    $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop

    $ouPath = Get-ADOrganizationalUnit -SearchBase $DomainOUPath -Filter { Name -eq $OUName } | Select-Object DistinguishedName
    if ($ouPath)
    {
        Write-Error "Hci organizational unit '$OUName' exists under '$DomainOUPath', to continue with '$OUName' OU please remove it and execute the tool."
    }

    try
    {
        New-ADOrganizationalUnit -Name $OUName -Path $DomainOUPath
        Write-Verbose "Successfully created $OUName organization unit under '$DomainOUPath' "
    }
    catch
    {
        Write-Error "Failed to create organization unit. Exception :: $_"
    }
}


<#
 .Synopsis
  Remove hci organization unit.
 
 .Parameter AsHciOUPath
  The hci ou path.
#>


function Remove-AsHciOU
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $AsHciOUPath
    )

    $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop

    try
    {
        if ([adsi]::Exists("LDAP://$AsHciOUPath"))
        {
            Get-ADOrganizationalUnit -Identity $AsHciOUPath | 
                Set-ADObject -ProtectedFromAccidentalDeletion:$false -PassThru |
                Remove-ADOrganizationalUnit -Recursive -Confirm:$false
            Write-Verbose "Successfully deleted $AsHciOUPath"
        }
        else
        {
            Write-Verbose "OU $AsHciOUPath not found, skipping the remove operation."
        }
    }
    catch
    {
        Write-Error "Unable to delete '$AsHciOUPath'. Exception :: $_"
    }
}

<#
 .Synopsis
  Creates hci deployment users under hci users organization unit path.
 
 .Parameter AsHciDeploymentUserName
  The azure stack hci deployment user names
 
 .Parameter DomainFQDN
  The active directory domain fqdn
 
 .Parameter AsHciUserPassword
  The azure stack hci user password.
 
 .Parameter HciUsersOUPath
  The hci users organization unit path object.
 
#>


function New-AsHciUser
{
    [CmdletBinding(SupportsShouldProcess=$true)]
    Param
    (
        [Parameter(Mandatory = $true)]
        [String]
        $AsHciDeploymentUserName,

        [Parameter(Mandatory = $true)]
        [String]
        $DomainFQDN,

        [Parameter(Mandatory = $true)]
        [SecureString]
        $AsHciUserPassword,

        [Parameter(Mandatory = $true)]
        [Microsoft.ActiveDirectory.Management.ADOrganizationalUnit]
        $AsHciUsersOUPath,

        [Parameter(Mandatory = $false)]
        [bool] 
        $Rerun = $false
    )

    $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop

    try
    {
        $user = Get-ADUser -SearchBase $AsHciUsersOUPath -Filter { Name -eq $AsHciDeploymentUserName }
        if ($user -and (-not $Rerun))
        {
            Write-Error "$AsHciDeploymentUserName exists, please provide an unique username."
        }
        if ($user)
        {
            Write-verbose "$AsHciDeploymentUserName exists under $AsHciUsersOUPath, skipping."
        }
        else
        {
            New-ADUser -Name $AsHciDeploymentUserName -AccountPassword $AsHciUserPassword -UserPrincipalName "$AsHciDeploymentUserName@$DomainFQDN" -Enabled $true -PasswordNeverExpires $true -path $AsHciUsersOUPath.DistinguishedName
            Write-Verbose "Successfully created '$AsHciDeploymentUserName' under '$AsHciUsersOUPath'"
        }
    }
    catch
    {
        if ($_ -match 'The operation failed because UPN value provided for addition/modification is not unique forest-wide')
        {
            Write-Error "UserPrincipalName '$AsHciDeploymentUserName@$DomainFQDN' already exists, please provide a different user name"
        }
        elseif ($_ -match 'The specified account already exists')
        {
            Write-Error "$AsHciDeploymentUserName already exists, please provide a different user name"
        }
        else
        {
            Write-Error "Unable to create $AsHciDeploymentUserName. Exception :: $_ "
        }
    }
}

<#
 .Synopsis
  Grants full access permissions of hci organization unit to hci deployment user.
 
 .Parameter HciDeploymentUserName
  The hci deployment user name.
 
 .Parameter HciOUPath
  The hci organization unit path.
#>

function Grant-HciOuPermissionsToHciDeploymentUser
{
    [CmdletBinding(SupportsShouldProcess=$true)]
    Param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $AsHciDeploymentUserName,

        [Parameter(Mandatory = $true)]
        [Microsoft.ActiveDirectory.Management.ADOrganizationalUnit]
        $AsHciOUPath

    )

    $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop
    try
    {
        $ouPath = "AD:\$($AsHciOUPath.DistinguishedName)"

        $userSecurityIdentifier = Get-ADuser -Identity $AsHciDeploymentUserName
        $userSID = [System.Security.Principal.SecurityIdentifier] $userSecurityIdentifier.SID
        $acl = Get-Acl -Path $ouPath
        $userIdentityReference = [System.Security.Principal.IdentityReference] $userSID
        $adRight = [System.DirectoryServices.ActiveDirectoryRights] "GenericAll"
        $type = [System.Security.AccessControl.AccessControlType] "Allow"
        $inheritanceType = [System.DirectoryServices.ActiveDirectorySecurityInheritance] "All"
        $rule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($userIdentityReference, $adRight, $type, $inheritanceType)
        $acl.AddAccessRule($Rule)
        Set-Acl -Path $ouPath -AclObject $acl

        (Get-Acl $ouPath).Access | Where-Object IdentityReference -eq $userSID
        Write-Verbose "Successfully granted access permissions '$($AsHciOUPath.DistinguishedName)' to '$AsHciDeploymentUserName'"
    }
    catch
    {
        Write-Error "Failed to grant access permissions '$($AsHciOUPath.DistinguishedName)' to '$AsHciDeploymentUserName'. Error :: $_"
    }
}

<#
 .Synopsis
  Pre creates computer object in AD under hci computers ou path.
 
 .Parameter Machines
  The list of machines required for hci deployment.
 
 .Parameter DomainFQDN
  The domain fqdn.
 
 .Parameter HciComputerOuPath
  The hci computers ou path.
#>


function New-MachineObject
{
    [CmdletBinding(SupportsShouldProcess=$true)]
    Param
    (
        [Parameter(Mandatory = $true)]
        [String[]]
        $Machines,

        [Parameter(Mandatory = $true)]
        [String]
        $DomainFQDN,

        [Parameter(Mandatory = $true)]
        [Microsoft.ActiveDirectory.Management.ADOrganizationalUnit]
        $AsHciComputerOuPath
    )

    $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop

    foreach ($node in $Machines)
    {
        try
        {
            $computerObject = Get-ADComputer -SearchBase $($AsHciComputerOuPath.DistinguishedName) -Filter {Name -eq $node }
            if ($computerObject)
            {
                Write-Verbose "$node exists in AD, skipping computer object creation."
            }
            else
            {
                New-ADComputer -Name $node -SAMAccountName $node -DNSHostName "$node.$DomainFQDN" -Path $AsHciComputerOuPath
                Write-Verbose "Successfully created $node computer object under $($AsHciComputerOuPath.DistinguishedName)"
            }
        }
        catch
        {
            if ($_ -match 'The specified account already exists')
            {
                Write-Error "$node object already exists, move $node object under '$($AsHciComputerOuPath.DistinguishedName)'"
            }
            else
            {
                Write-Error "Error :: $_"
            }
        }
    }
}


<#
  .Synopsis
  Enables dynamic updates in the DNS zone corresponding to the domain and gives NC VMs permissions to update it.
 
  .Parameter DomainFQDN
  The domain fqdn.
 
  .Parameter DeploymentPrefix
  The deployment domain prefix.
 
  .Parameter OrganizationalUnit
  The OrganizationalUnit object under which the NC computer objects will be created
 
  .Parameter DNSServerIP
  IP of the DNS Server.
#>


function Set-DynamicDNSAndConfigureDomainZoneForNC
{
    [CmdletBinding(SupportsShouldProcess=$true)]
    Param
    (
        [Parameter(Mandatory = $true)]
        [String]
        $DomainFQDN,

        [Parameter(Mandatory = $true)]
        [string]
        $DeploymentPrefix,

        [Parameter(Mandatory = $true)]
        [Microsoft.ActiveDirectory.Management.ADOrganizationalUnit]
        $OrganizationalUnit,

        [Parameter(Mandatory=$false)]
        [string] $DNSServerIP
    )

    $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop

    try
    {
        $dnsZone = Get-DnsServerZone -Name $DomainFQDN -ComputerName $DNSServerIP
        if ($dnsZone.DynamicUpdate -eq "NonsecureAndSecure")
        {
            Write-Verbose "DNS server is configured to accept dynamic updates from secure and non-secure sources. No further DNS configuration changes are needed."
            return
        }

        Write-Verbose "Configuring DNS server to accept secure dynamic updates. Identities with the right access will be allowed to update DNS entries."
        Set-DnsServerPrimaryZone -Name $DomainFQDN -ComputerName $DNSServerIP -DynamicUpdate Secure

        $ncVmNames = @("$DeploymentPrefix-NC01", "$DeploymentPrefix-NC02", "$DeploymentPrefix-NC03")

        Write-Verbose "Pre-creating the following computer objects for the Network Controller nodes: $ncVmNames."
        New-MachineObject -Machines $ncVmNames -DomainFQDN $DomainFQDN -AsHciComputerOuPath $OrganizationalUnit -Verbose

        $domainZoneAcl = Get-Acl "AD:\$($dnsZone.DistinguishedName)"

        foreach ($ncVm in $ncVmNames)
        {
            Write-Verbose "Giving access to $ncVm to allow it to do DNS dynamic updates."

            $adComputer = Get-ADComputer $ncVm
            $ace = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $adComputer.SID,'GenericAll','Allow'
            $domainZoneAcl.AddAccessRule($ace)
        }

        Set-Acl -AclObject $domainZoneAcl "AD:\$($dnsZone.DistinguishedName)"

        Write-Output "List of AD identities with write permissions that are allowed to do DNS dynamic updates to DNS zone: $DomainFQDN."

        $domainZoneAcl.Access | where -FilterScript {($_.ActiveDirectoryRights -band `
            [System.DirectoryServices.ActiveDirectoryRights]::GenericWrite) -eq `
            [System.DirectoryServices.ActiveDirectoryRights]::GenericWrite -and `
            $_.AccessControlType -eq [System.Security.AccessControl.AccessControlType]::Allow} `
            | select @{n="Identities";e={$_.IdentityReference}}
    }
    catch
    {
        Write-Error "Failed to configure the DNS server to support dynamic updates. Please enable dynamic updates manually in your DNS server."
    }
}


<#
 .Synopsis
  Prestage cluster computer object in AD
  Refer to https://docs.microsoft.com/en-us/windows-server/failover-clustering/prestage-cluster-adds
 
 .Parameter OrganizationalUnit
  The OrganizationalUnit object under which to create the cluster computer object
 
 .Parameter ClusterName
  The cluster name
 
 .Example
  $ou = Get-ADOrganizationalUnit -Filter 'Name -eq "testou"'
  New-ADPrestagedCluster -OrganizationalUnit $ou -ClusterName 's-cluster'
#>

function New-PrestagedAsHciCluster
{
    [CmdletBinding(SupportsShouldProcess=$true)]
    param(
          [Parameter(Mandatory=$true)]
          [ValidateNotNull()]
          [Microsoft.ActiveDirectory.Management.ADOrganizationalUnit]
          $OrganizationalUnit,

          [Parameter(Mandatory=$true)]
          [ValidateNotNullOrEmpty()]
          [string]
          $ClusterName
    )

    $ErrorActionPreference = "Stop"

    Write-Verbose "Checking computer '$ClusterName' in AD ..." -Verbose

    $cno = Get-ADComputer -Filter "Name -eq '$ClusterName'"

    if ($cno)
    {
        Write-Error "Found existing computer with name '$ClusterName', please check the current usage of this object. To continue with the '$ClusterName' delete the exisiting object and run the tool."
    }

    Write-Verbose "Creating computer '$ClusterName' under OU '$($OrganizationalUnit.DistinguishedName)' ..." -Verbose

    $cno = New-ADComputer -Name $ClusterName -Description 'Cluster Name Object of HCI deployment' -Path $OrganizationalUnit.DistinguishedName -Enabled $false -PassThru -Verbose
    $cno | Set-ADObject -ProtectedFromAccidentalDeletion:$true -Verbose

    Write-Verbose "Configuring permission for computer '$ClusterName' ..." -Verbose

    $ouPath = "AD:\$($OrganizationalUnit.DistinguishedName)"
    $ouAcl = Get-Acl $ouPath
    $ouAclUpdate = New-Object System.DirectoryServices.ActiveDirectorySecurity

    foreach ($ace in $ouAcl.Access)
    {
        if ($ace.IdentityReference -notlike "*\$ClusterName$")
        {
            $ouAclUpdate.AddAccessRule($ace)
        }
    }

    # Refer to https://docs.microsoft.com/en-us/windows/win32/adschema/c-computer
    $computersObjectType = [System.Guid]::New('bf967a86-0de6-11d0-a285-00aa003049e2')
    $allObjectType = [System.Guid]::Empty
    $ace1 = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $cno.SID, "CreateChild", "Allow", $computersObjectType, "All"
    $ace2 = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $cno.SID, "ReadProperty", "Allow", $allObjectType, "All"

    $ouAclUpdate.AddAccessRule($ace1)
    $ouAclUpdate.AddAccessRule($ace2)

    $ouAclUpdate | Set-Acl $ouPath -Verbose

    (Get-Acl $ouPath).Access | Where-Object IdentityReference -like "*\$ClusterName$"

    Write-Verbose "Finish prestage for cluster '$ClusterName'." -Verbose
}


<#
 .Synopsis
  Creates required security groups for hci deployment.
 
 .Parameter HciUsersOuPath
  The hci users organization unit object.
 
 .Parameter HciComputersOuPath
  The hci computers organization unit object.
 
 .Parameter DeploymentPrefix
  The hci deployment prefix.
 
 .Parameter PhysicalMachines
  The hci cluster physical machines.
 
 .Parameter HciDeploymentUserName
  The hci deployment username.
#>

function New-AsHciSecurityGroup
{
    [CmdletBinding(SupportsShouldProcess=$true)]
    Param
    (
        [Parameter(Mandatory = $true)]
        [Microsoft.ActiveDirectory.Management.ADOrganizationalUnit]
        $AsHciUsersOuPath,

        [Parameter(Mandatory = $true)]
        [Microsoft.ActiveDirectory.Management.ADOrganizationalUnit]
        $AsHciComputersOuPath,

        [Parameter(Mandatory = $true)]
        [string]
        $DeploymentPrefix,

        [Parameter(Mandatory = $false)]
        [string[]]
        $PhysicalMachines,

        [Parameter(Mandatory = $true)]
        [string]
        $AsHciDeploymentUserName
    )

    $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop

    Write-Verbose "Creating security groups under '$($AsHciUsersOuPath.DistinguishedName)'"
    # List of security groups required for hci deployment.
    $securityGroups = @("$($DeploymentPrefix)-Sto-SG",
                     "$($DeploymentPrefix)-FsAcl-InfraSG",
                     "$($DeploymentPrefix)-FsAcl-AcsSG",
                     "$($DeploymentPrefix)-CertificateReadersSG",
                     "$($DeploymentPrefix)-Slb-VmSG",
                     "$($DeploymentPrefix)-Gw-VmSG",
                     "$($DeploymentPrefix)-FsAcl-SqlSG",
                     "$($DeploymentPrefix)-Fab-SrvSG",
                     "$($DeploymentPrefix)-HA-SrvSG",
                     "$($DeploymentPrefix)-EceSG",
                     "$($DeploymentPrefix)-BM-ECESG",
                     "$($DeploymentPrefix)-HA-R-SrvSG",
                     "$($DeploymentPrefix)-SB-Jea-LC-VmSG",
                     "$($DeploymentPrefix)-Hc-Rs-SrvSG",
                     "$($DeploymentPrefix)-Agw-SrvSG",
                     "$($DeploymentPrefix)-Hrp-HssSG",
                     "$($DeploymentPrefix)-IH-HsSG",
                     "$($DeploymentPrefix)-NC-VmSG",
                     "$($DeploymentPrefix)-Nc-BmSG",
                     "$($DeploymentPrefix)-OpsAdmin",
                     "$($DeploymentPrefix)-SB-Jea-MG-VmSG",
                     "$($DeploymentPrefix)-Nc-EceSG",
                     "$($DeploymentPrefix)-NC-FsSG",
                     "$($DeploymentPrefix)-Slb-EceSG",
                     "$($DeploymentPrefix)-FsAcl-PublicSG",
                     "$($DeploymentPrefix)-FRA-SrvSG",
                     "$($DeploymentPrefix)-IH-MsSG"
                     )


    foreach ($securityGroup in $securityGroups)
    {
        try
        {
            $adGroup = Get-AdGroup -SearchBase $($AsHciUsersOuPath.DistinguishedName) -Filter { Name -eq $SecurityGroup}
            if ($adGroup)
            {
                Write-Verbose "$securityGroup is present on AD hence skipping."
            }
            else
            {
                New-ADGroup -Name $SecurityGroup -DisplayName $securityGroup -Description $securityGroup -GroupCategory Security -GroupScope DomainLocal -Path $($AsHciUsersOuPath.DistinguishedName)
            }
            Write-Verbose "$securityGroup successfully created."
        }
        catch
        {
            Write-Error "$SecurityGroup creation failed. Error :: $_ "
        }
    }

    Write-Verbose "Successfully created security groups under '$($AsHciUsersOuPath.DistinguishedName)'"

    $physicalMachineSecurityGroups = @("$($DeploymentPrefix)-Sto-SG")
    #Add physical machines to the security groups.
    foreach ($physicalMachine in $PhysicalMachines)
    {
        $machineObject = Get-ADComputer -SearchBase $($AsHciComputersOuPath.DistinguishedName) -Filter {Name -eq $physicalMachine}
        Add-Membership -IdentityObject $machineObject -SecurityGroupNames $physicalMachineSecurityGroups -AsHciUsersOUPath $($AsHciUsersOuPath.DistinguishedName)
    }

    #Add user membership.
    $AsHciUserSecurityGroups = @("$($AsHciDeploymentPrefix)-OpsAdmin", "$($DeploymentPrefix)-EceSG", "$($DeploymentPrefix)-BM-ECESG", "$($DeploymentPrefix)-FsAcl-InfraSG", "$($DeploymentPrefix)-FsAcl-AcsSG", "$($DeploymentPrefix)-Nc-EceSG")
    $userIdentity = Get-ADUser -SearchBase $AsHciUsersOuPath.DistinguishedName -Filter { Name -eq $AsHciDeploymentUserName }
    Add-Membership -IdentityObject $userIdentity -SecurityGroupNames $AsHciUserSecurityGroups -AsHciUsersOuPath $AsHciUsersOuPath.DistinguishedName
}

<#
 .Synopsis
  Creates required group managed service accounts for hci deployment.
 
 .Parameter DomainFQDN
  The domain fqdn.
 
 .Parameter AsHciUsersOuPath
  The hci users organization unit object.
#>


function New-AsHciGmsaAccount
{
    [CmdletBinding(SupportsShouldProcess=$true)]
    Param
    (

        [Parameter(Mandatory = $true)]
        [String]
        $DomainFQDN,

        [Parameter(Mandatory = $true)]
        [String]
        $DeploymentPrefix,

        [Parameter(Mandatory = $true)]
        [Microsoft.ActiveDirectory.Management.ADOrganizationalUnit]
        $AsHciUsersOuPath

    )

    $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop

    # List of gMSA accounts, it's membership, principals allowed to retrieve the password and service principal names if any.
    $gmsaAccounts =
    @([pscustomobject]@{ GmsaName="$($DeploymentPrefix)-BM-ECE";
                         PrincipalsAllowedToRetrieveManagedPassword= @("$($DeploymentPrefix)-Sto-SG");
                         ServicePrincipalName=@("$($DeploymentPrefix)/ae3299a9-3e87-4186-bd99-c43c9ae6a571");
                         MemberOf=@("$($DeploymentPrefix)-FsAcl-SqlSG", "$($DeploymentPrefix)-Fab-SrvSG", "$($DeploymentPrefix)-HA-SrvSG", "$($DeploymentPrefix)-EceSG", "$($DeploymentPrefix)-BM-ECESG", "$($DeploymentPrefix)-FsAcl-InfraSG")},

    [pscustomobject]@{ GmsaName="$($DeploymentPrefix)-BM-ALM";
                         PrincipalsAllowedToRetrieveManagedPassword= @("$($DeploymentPrefix)-Sto-SG");
                         ServicePrincipalName=@();
                         MemberOf=@("$($DeploymentPrefix)-FsAcl-InfraSG")},

    [pscustomobject]@{ GmsaName="$($DeploymentPrefix)-NC-ALM";
                         PrincipalsAllowedToRetrieveManagedPassword= @("$($DeploymentPrefix)-Nc-VmSG");
                         ServicePrincipalName=@();
                         MemberOf=@("$($DeploymentPrefix)-FsAcl-InfraSG")},

    [pscustomobject]@{ GmsaName="$($DeploymentPrefix)-LB-ALM";
                         PrincipalsAllowedToRetrieveManagedPassword= @("$($DeploymentPrefix)-Slb-VmSG");
                         ServicePrincipalName=@();
                         MemberOf=@("$($DeploymentPrefix)-FsAcl-InfraSG")},

    [pscustomobject]@{ GmsaName="$($DeploymentPrefix)-GW-ALM";
                         PrincipalsAllowedToRetrieveManagedPassword= @("$($DeploymentPrefix)-Gw-VmSG");
                         ServicePrincipalName=@();
                         MemberOf=@("$($DeploymentPrefix)-FsAcl-InfraSG")},

    [pscustomobject]@{ GmsaName="$($DeploymentPrefix)-BM-FCA";
                         PrincipalsAllowedToRetrieveManagedPassword= @("$($DeploymentPrefix)-Sto-SG");
                         ServicePrincipalName=@();
                         MemberOf=@("$($DeploymentPrefix)-FsAcl-InfraSG")},

    [pscustomobject]@{ GmsaName="$($DeploymentPrefix)-NC-FCA";
                         PrincipalsAllowedToRetrieveManagedPassword= @("$($DeploymentPrefix)-Nc-VmSG");
                         ServicePrincipalName=@();
                         MemberOf=@("$($DeploymentPrefix)-FsAcl-InfraSG")},

    [pscustomobject]@{ GmsaName="$($DeploymentPrefix)-LB-FCA";
                         PrincipalsAllowedToRetrieveManagedPassword= @("$($DeploymentPrefix)-Slb-VmSG");
                         ServicePrincipalName=@();
                         MemberOf=@("$($DeploymentPrefix)-FsAcl-InfraSG")},

    [pscustomobject]@{ GmsaName="$($DeploymentPrefix)-GW-FCA";
                         PrincipalsAllowedToRetrieveManagedPassword= @("$($DeploymentPrefix)-Gw-VmSG");
                         ServicePrincipalName=@();
                         MemberOf=@("$($DeploymentPrefix)-FsAcl-InfraSG")},

    [pscustomobject]@{ GmsaName="$($DeploymentPrefix)-BM-FRA";
                         PrincipalsAllowedToRetrieveManagedPassword= @("$($DeploymentPrefix)-Sto-SG");
                         ServicePrincipalName=@();
                         MemberOf=@("$($DeploymentPrefix)-FsAcl-InfraSG")},

    [pscustomobject]@{ GmsaName="$($DeploymentPrefix)-NC-FRA";
                         PrincipalsAllowedToRetrieveManagedPassword= @("$($DeploymentPrefix)-Nc-VmSG");
                         ServicePrincipalName=@();
                         MemberOf=@("$($DeploymentPrefix)-FsAcl-InfraSG")},

    [pscustomobject]@{ GmsaName="$($DeploymentPrefix)-BM-TCA";
                         PrincipalsAllowedToRetrieveManagedPassword= @("$($DeploymentPrefix)-Sto-SG");
                         ServicePrincipalName=@();
                         MemberOf=@("$($DeploymentPrefix)-FsAcl-InfraSG")},

    [pscustomobject]@{ GmsaName="$($DeploymentPrefix)-NC-TCA";
                         PrincipalsAllowedToRetrieveManagedPassword= @("$($DeploymentPrefix)-Nc-VmSG");
                         ServicePrincipalName=@();
                         MemberOf=@("$($DeploymentPrefix)-FsAcl-InfraSG")},

    [pscustomobject]@{ GmsaName="$($DeploymentPrefix)-Nc-HA";
                         PrincipalsAllowedToRetrieveManagedPassword= @("$($DeploymentPrefix)-Nc-VmSG");
                         ServicePrincipalName=@("$($DeploymentPrefix)/NC/1b4dde6b-7ea8-407a-8c9e-f86e8b97fd1c");
                         MemberOf=@("$($DeploymentPrefix)-FsAcl-InfraSG", "$($DeploymentPrefix)-HA-R-SrvSG")},

    [pscustomobject]@{ GmsaName="$($DeploymentPrefix)-BM-HA";
                         PrincipalsAllowedToRetrieveManagedPassword= @("$($DeploymentPrefix)-Sto-SG");
                         ServicePrincipalName=@("$($DeploymentPrefix)/PhysicalNode/1b4dde6b-7ea8-407a-8c9e-f86e8b97fd1c");
                         MemberOf=@("$($DeploymentPrefix)-FsAcl-InfraSG", "$($DeploymentPrefix)-HA-R-SrvSG")},

    [pscustomobject]@{ GmsaName="$($DeploymentPrefix)-SB-LC";
                         PrincipalsAllowedToRetrieveManagedPassword= @("$($DeploymentPrefix)-Sto-SG", "$($DeploymentPrefix)-SB-Jea-LC-VmSG");
                         ServicePrincipalName=@("$($DeploymentPrefix)/754dbc04-8f91-4cb6-a10f-899dac573fa0");
                         MemberOf=@("$($DeploymentPrefix)-FsAcl-InfraSG", "$($DeploymentPrefix)-SB-Jea-LC-VmSG", "$($DeploymentPrefix)-Sto-SG" ) },

    [pscustomobject]@{ GmsaName="$($DeploymentPrefix)-SB-Jea";
                         PrincipalsAllowedToRetrieveManagedPassword= @("$($DeploymentPrefix)-Sto-SG");
                         ServicePrincipalName=@();
                         MemberOf=@("$($DeploymentPrefix)-FsAcl-InfraSG", "$($DeploymentPrefix)-Sto-SG", "$($DeploymentPrefix)-OpsAdmin" ) },

    [pscustomobject]@{ GmsaName="$($DeploymentPrefix)-SB-MG";
                         PrincipalsAllowedToRetrieveManagedPassword= @("$($DeploymentPrefix)-Sto-SG", "$($DeploymentPrefix)-SB-Jea-MG-VmSG");
                         ServicePrincipalName=@("$($DeploymentPrefix)/ea126685-c89e-4294-959f-bba6bf75b4aa");
                         MemberOf=@("$($DeploymentPrefix)-FsAcl-InfraSG", "$($DeploymentPrefix)-SB-Jea-MG-VmSG", "$($DeploymentPrefix)-Sto-SG" ) },

    [pscustomobject]@{ GmsaName="$($DeploymentPrefix)-SBJeaM";
                         PrincipalsAllowedToRetrieveManagedPassword= @("$($DeploymentPrefix)-Sto-SG");
                         ServicePrincipalName=@();
                         MemberOf=@("$($DeploymentPrefix)-FsAcl-InfraSG", "$($DeploymentPrefix)-Sto-SG" ) },

    [pscustomobject]@{ GmsaName="$($DeploymentPrefix)-NC-SA";
                         PrincipalsAllowedToRetrieveManagedPassword= @("$($DeploymentPrefix)-Nc-VmSG");
                         ServicePrincipalName=@();
                         MemberOf=@("$($DeploymentPrefix)-Nc-VmSG")},

    [pscustomobject]@{ GmsaName="$($DeploymentPrefix)-Nc-TS";
                         PrincipalsAllowedToRetrieveManagedPassword= @("$($DeploymentPrefix)-Nc-VmSG");
                         ServicePrincipalName=@();
                         MemberOf=@("$($DeploymentPrefix)-FsAcl-InfraSG")},

    [pscustomobject]@{ GmsaName="$($DeploymentPrefix)-EceSA";
                         PrincipalsAllowedToRetrieveManagedPassword= @("$($DeploymentPrefix)-Sto-SG");
                         ServicePrincipalName=@("$($DeploymentPrefix)/4dde37cc-6ee0-4d75-9444-7061e156507f");
                         MemberOf=@("$($DeploymentPrefix)-FsAcl-InfraSG", "$($DeploymentPrefix)-FsAcl-sqlSG", "$($DeploymentPrefix)-Fab-SrvSG", "$($DeploymentPrefix)-HA-SrvSG", "$($DeploymentPrefix)-EceSG", "$($DeploymentPrefix)-BM-EceSG",  "$($DeploymentPrefix)-Nc-EceSG")},

    [pscustomobject]@{ GmsaName="$($DeploymentPrefix)-NC-ECE";
                         PrincipalsAllowedToRetrieveManagedPassword= @("$($DeploymentPrefix)-Nc-VmSG");
                         ServicePrincipalName=@("$($DeploymentPrefix)/128fd370-07e4-41ac-876f-738c3c4cfd1b");
                         MemberOf=@("$($DeploymentPrefix)-FsAcl-sqlSG", "$($DeploymentPrefix)-Fab-SrvSG", "$($DeploymentPrefix)-HA-SrvSG","$($DeploymentPrefix)-EceSG", "$($DeploymentPrefix)-Nc-EceSG", "$($DeploymentPrefix)-FsAcl-InfraSG")},


    [pscustomobject]@{ GmsaName="$($DeploymentPrefix)-Urp-SA";
                         PrincipalsAllowedToRetrieveManagedPassword= @("$($DeploymentPrefix)-Sto-SG");
                         ServicePrincipalName=@("$($DeploymentPrefix)/110bac92-1879-47ae-9611-e40f8abf4fc0");
                         MemberOf=@("$($DeploymentPrefix)-FsAcl-PublicSG", "$($DeploymentPrefix)-FsAcl-InfraSG", "$($DeploymentPrefix)-BM-ECESG", "$($DeploymentPrefix)-Fab-SrvSG",  "$($DeploymentPrefix)-EceSG")}, 

    [pscustomobject]@{ GmsaName="$($DeploymentPrefix)-IH-Hs";
                         PrincipalsAllowedToRetrieveManagedPassword= @("$($DeploymentPrefix)-Sto-SG");
                         ServicePrincipalName=@("$($DeploymentPrefix)/d472df56-c8cb-4fa0-80ab-2ec4994c0798");
                         MemberOf=@("$($DeploymentPrefix)-IH-HsSG", "$($DeploymentPrefix)-FsAcl-InfraSG", "$($DeploymentPrefix)-HA-R-SrvSG", "$($DeploymentPrefix)-FRA-SrvSG")},
                         
    [pscustomobject]@{ GmsaName="$($DeploymentPrefix)-BM-MSA";
                         PrincipalsAllowedToRetrieveManagedPassword= @("$($DeploymentPrefix)-Sto-SG");
                         ServicePrincipalName=@("$($DeploymentPrefix)/PhysicalNode/d8c180f6-7290-458e-90f0-96894f45e981");
                         MemberOf=@("$($DeploymentPrefix)-FsAcl-InfraSG", "$($DeploymentPrefix)-IH-MsSG", "$($DeploymentPrefix)-HA-R-SrvSG")})

    # Creating gmsa accounts.
    foreach ($gmsaAccount in $GmsaAccounts)
    {
        try
        {
            $gmsaName = $($gmsaAccount.GmsaName)
            $adGmsaAccount = Get-ADServiceAccount -SearchBase $($AsHciUsersOuPath.DistinguishedName) -Filter {Name -eq $gmsaName}
            if ($adGmsaAccount)
            {
                Write-Verbose "$gmsaName exists hence skipping."
            }
            else
            {
                if ($($gmsaAccount.ServicePrincipalName).count -ge 1)
                {
                    New-ADServiceAccount -Name $gmsaName -DNSHostName "$($gmsaAccount.GmsaName).$DomainFQDN" -ServicePrincipalNames $($gmsaAccount.ServicePrincipalName) -PrincipalsAllowedToRetrieveManagedPassword $($gmsaAccount.PrincipalsAllowedToRetrieveManagedPassword) -ManagedPasswordIntervalInDays 1 -KerberosEncryptionType AES256 -Path $($AsHciUsersOuPath.DistinguishedName)
                }
                else
                {
                    New-ADServiceAccount -Name $gmsaName -DNSHostName "$($gmsaAccount.GmsaName).$DomainFQDN" -PrincipalsAllowedToRetrieveManagedPassword $($gmsaAccount.PrincipalsAllowedToRetrieveManagedPassword) -ManagedPasswordIntervalInDays 1 -KerberosEncryptionType AES256 -Path $($AsHciUsersOuPath.DistinguishedName)
                }
                Write-Verbose "Successfully created $gmsaName on AD"
            }
            $serviceAccountIdentityObject = Get-ADServiceAccount -SearchBase $($AsHciUsersOuPath.DistinguishedName) -Filter {Name -eq $gmsaName}
            Add-Membership -IdentityObject $serviceAccountIdentityObject -SecurityGroupNames $($gmsaAccount.MemberOf) -AsHciUsersOuPath $($AsHciUsersOuPath.DistinguishedName)
        }
        catch
        {
            if ($_ -match 'The operation failed because SPN value provided for addition/modification is not unique forest-wide')
            {
                Write-Error "SPN '$($gmsaAccount.ServicePrincipalName)' already exists, please remove the SPN and Rerun the tool."
            }
            Write-Error "Failed to create $gmsaName or adding it's membership. Error :: $_"
        }
    }
}

<#
 .Synopsis
  Add security group membership to other required security groups.
 
 .Parameter HciUsersOuPath
  The hci users ou path.
 
#>


function Add-GroupMembership
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory = $true)]
        [Microsoft.ActiveDirectory.Management.ADOrganizationalUnit]
        $AsHciUsersOuPath,

        [Parameter(Mandatory = $true)]
        [String]
        $DeploymentPrefix
    )

    $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop

    # Security group memberships.
    $SecurityGroupMemberships =
    @([pscustomobject]@{ Name="$($DeploymentPrefix)-HA-R-SrvSG";
                         MemberOf=@("$($DeploymentPrefix)-Hc-Rs-SrvSG", "$($DeploymentPrefix)-Agw-SrvSG", "$($DeploymentPrefix)-Hrp-HssSG", "$($DeploymentPrefix)-IH-HsSG", "$($DeploymentPrefix)-IH-MsSG", "$($DeploymentPrefix)-FsAcl-InfraSG")} ,

    [pscustomobject]@{ Name="$($DeploymentPrefix)-NC-VmSG";
                         MemberOf=@("$($DeploymentPrefix)-FsAcl-AcsSG", "$($DeploymentPrefix)-FsAcl-InfraSG")})


    foreach ($securityGroupMembership in $SecurityGroupMemberships)
    {
        $group = $securityGroupMembership.Name
        $SecurityGroupNames = $securityGroupMembership.MemberOf
        $groupObject = Get-ADGroup -SearchBase $($AsHciUsersOuPath.DistinguishedName) -Filter {Name -eq $group}
        Add-Membership -IdentityObject $groupObject -SecurityGroupNames $SecurityGroupNames -AsHciUsersOuPath $AsHciUsersOuPath
    }
}


<#
 .Synopsis
  Helper function to add the membership of an identity object to the security group name.
 
 .Parameter IdentityObject
  The ad identity object.
 
 .Parameter SecurityGroupNames
  The list of security group names.
 
 .Parameter HciUsersOuPath
  The hci users ou path.
 
#>


function Add-Membership
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory = $true)]
        $IdentityObject,

        [Parameter(Mandatory = $true)]
        $SecurityGroupNames,

        [Parameter(Mandatory = $true)]
        $AsHciUsersOuPath
    )

    $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop
    foreach ($securityGroupName in $SecurityGroupNames)
    {
        try
        {
            $groupObject =  Get-ADGroup -SearchBase $AsHciUsersOuPath -Filter {Name -eq $securityGroupName}
            if (-not $groupObject)
            {
                Write-Error "$securityGroupName is not available."
            }

            $isMember = Get-ADGroupMember -Identity $groupObject |Where-Object {$_.Name -eq $($IdentityObject.Name)}
            if ($isMember)
            {
                Write-Verbose "$($IdentityObject.Name) is already a member of $securityGroupName hence skipping"
            }
            else
            {
                Add-ADGroupMember -Identity $groupObject -Members $IdentityObject
                Write-Verbose "Finished adding '$($IdentityObject.Name)' as a member of the group '$securityGroupName'."
            }
        }
        catch
        {
            Write-Error "Failed to add the $($IdentityObject.Name) to $securityGroupName. Error :: $_"
        }
    }
}

<#
 .Synopsis
  Blocks gpo inheritance to Hci ou
 
 .Parameter HciOUs
  List of hci ou
#>


function Block-GPInheritance
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory = $true)]
        [Microsoft.ActiveDirectory.Management.ADOrganizationalUnit[]]
        $AsHciOUs
    )

    $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop
    foreach ($asHciOu in $AsHciOUs)
    {
        try
        {
            $gpInheritance = Get-GPInheritance -Target $($asHciOu.DistinguishedName)
            if (-not $gpInheritance.GpoInheritanceBlocked)
            {
                $gpInheritance = Set-GPInheritance -Target $($asHciOu.DistinguishedName) -IsBlocked Yes
            }
            Write-Verbose "Gpo inheritance blocked for '$($asHciOu.DistinguishedName)', inheritance blocked state is : $($gpInheritance.GpoInheritanceBlocked)"
        }
        catch
        {
            Write-Error "Failed to block gpo inheritance for '$($asHciOu.DistinguishedName)'. Error :: $_"
        }
    }
}

<#
 .Synopsis
  Verfies the below obejcts are unique or not.
    Deployment user
    Organizational unit
    Phyiscal machines
    Deployment prefix
    Cluster name
 
 .Parameter AsHciDeploymentUserCredential
  Deployment credentails.
 
 .Parameter AsHciOUName
  Organizational unit.
 
 .Parameter AsHciPhysicalNodeList
  Physcal machines list.
 
 .Parameter AsHciDeploymentPrefix
  Deployment prefix.
 
 .Parameter AsHciClusterName
  Cluster name.
 
#>


function Test-UniqueAdObjects
{
    Param(
        [PSCredential] $AsHciDeploymentUserCredential,

        [string] $AsHciOUName,

        [string[]] $AsHciPhysicalNodeList,

        [string] $AsHciDeploymentPrefix,

        [string] $AsHciClusterName
    )

    $Errors = New-Object System.Collections.Generic.List[System.Object]

    $asHciDeploymentUserName = $AsHciDeploymentUserCredential.UserName

    if ( Get-ADUser -Filter { Name -eq $asHciDeploymentUserName })
    {
        $Errors.Add(" UserName :: '$asHciDeploymentUserName'")
    }

    if (-not ([string]::IsNullOrWhitespace($AsHciClusterName))) 
    {
        if (Get-ADComputer -Filter "Name -eq '$AsHciClusterName'")
        {
            $Errors.Add(" Cluster Name :: '$AsHciClusterName'")
        }
    }

    foreach ($node in $AsHciPhysicalNodeList)
    {
        if (Get-ADComputer -Filter "Name -eq '$node'")
        {
            $Errors.Add(" Physical Node :: '$node'")
        }
    }

    $opsAdminGroup = "$AsHciDeploymentPrefix-OpsAdmin"
    if (Get-AdGroup -Filter { Name -eq $opsAdminGroup} )
    {
        $Errors.Add(" Deployment prefix :: '$AsHciDeploymentPrefix'")
    }


    if ($Errors.Count -ge 1)
    {
        Write-Warning "Below object/objects are already available on AD please provide unique values and run the tool."
        foreach ($error in $Errors)
        {
            Write-Warning "$error"
        }
        Write-Error "AD precreation object failed"
    }
}

<#
 .Synopsis
  Creates required active directory objects for deployment / upgrade driver execution.
 
 .Parameter Deploy
  Deployment.
 
 .Parameter Upgrade
  Upgrade.
 
 .Parameter AsHciDeploymentUserCredential
  Deployment credentails.
 
 .Parameter AsHciOUName
  Organizational unit.
 
 .Parameter AsHciPhysicalNodeList
  Physcal machines list.
 
 .Parameter AsHciDeploymentPrefix
  Deployment prefix.
 
 .Parameter AsHciClusterName
  Cluster name.
 
 .Parameter DomainFQDN
  Domain FQDN.
 
 .Parameter DNSServerIP
  DNS server ipaddress,
#>


function New-AdObjectsForDeployOrUpgrade
{
    Param(
        [Switch] $Deploy,

        [Switch] $Upgrade,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [PSCredential] $AsHciDeploymentUserCredential,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string] $AsHciOUName,

        [Parameter(Mandatory = $true)]
        [ValidateLength(1,8)]
        [string] $AsHciDeploymentPrefix,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string] $DomainFQDN,

        [string[]] $AsHciPhysicalNodeList,

        [string] $AsHciClusterName,

        [string] $DNSServerIP

    )

    # OU names
    $hciOUName = ($AsHciOUName.Split(",",2)[0]).split("=")[1]
    $computersOU = "Computers"
    $usersOU = "Users"

    # Create Hci organization units
    New-AsHciOrganizationalUnit -DomainOUPath $hciParentOUPath -OUName $hciOUName -Verbose
    $asHciOUPath = Get-ADOrganizationalUnit -SearchBase $hciParentOUPath -Filter { Name -eq $hciOUName }

    # Create computers OU under $asHciOUPath
    New-AsHciOrganizationalUnit -DomainOUPath $($asHciOUPath.DistinguishedName) -OUName $computersOU -Verbose
    $asHciComputersOUPath = Get-ADOrganizationalUnit -SearchBase $asHciOUPath.DistinguishedName -Filter { Name -eq $computersOU }

    # Create users OU under $asHciOUPath
    New-AsHciOrganizationalUnit -DomainOUPath $($asHciOUPath.DistinguishedName) -OUName $usersOU -Verbose
    $asHciUsersOUPath = Get-ADOrganizationalUnit -SearchBase $asHciOUPath.DistinguishedName -Filter { Name -eq $usersOU }

    # Create Hci deployment user under Hci users OU path
    $asHciDeploymentUserName = $AsHciDeploymentUserCredential.UserName
    $asHciDeploymentUserPassword = $AsHciDeploymentUserCredential.Password
    New-AsHciUser -AsHciDeploymentUserName $asHciDeploymentUserName -AsHciUserPassword $asHciDeploymentUserPassword -DomainFQDN $DomainFQDN -AsHciUsersOUPath $asHciUsersOUPath  -Verbose

    # Grant permissions to hci deployment user
    Grant-HciOuPermissionsToHciDeploymentUser -AsHciDeploymentUserName $asHciDeploymentUserName -AsHciOUPath $asHciOUPath -Verbose

    
    if ($Deploy)
    {
        # Pre-create computer objects
        New-MachineObject -Machines $AsHciPhysicalNodeList -DomainFQDN $DomainFQDN -AsHciComputerOuPath $asHciComputersOUPath -Verbose
        
        # Pre-create cluster objects
        New-PrestagedAsHciCluster -OrganizationalUnit $asHciComputersOUPath -ClusterName $AsHciClusterName -Verbose
    }
        
    # Create security groups
    New-AsHciSecurityGroup -AsHciUsersOuPath $asHciUsersOUPath -AsHciComputersOuPath $asHciComputersOUPath -PhysicalMachines $AsHciPhysicalNodeList -DeploymentPrefix $AsHciDeploymentPrefix -AsHciDeploymentUserName $asHciDeploymentUserName -Verbose

    # Create gMSA accounts
    New-AsHciGmsaAccount -DomainFQDN $DomainFQDN -DeploymentPrefix $AsHciDeploymentPrefix -AsHciUsersOuPath $AsHciUsersOuPath -Verbose

    # Add security group membership to other security groups.
    Add-GroupMembership -AsHciUsersOuPath $AsHciUsersOuPath -DeploymentPrefix $AsHciDeploymentPrefix  -Verbose

    # Block gpo inheritance to hci ou
    Block-GPInheritance -AsHciOUs @($asHciOUPath, $asHciUsersOUPath,$asHciComputersOUPath) -Verbose

    if ($DNSServerIP)
    {
        # Configure the DNS to support dynamic updates and give NC VMs permissions to update the domain zone
        Set-DynamicDNSAndConfigureDomainZoneForNC -DomainFQDN $DomainFQDN -DeploymentPrefix $AsHciDeploymentPrefix -OrganizationalUnit $asHciComputersOUPath -DNSServerIP $DNSServerIP -Verbose
    }
    else
    {
        Write-Warning "DNSServerIP parameter was not provided, so configuring dynamic DNS updates on the server was skipped. Please make sure that your DNS supports dynamic updates and that Network Controller VMs have access to do DNS updates."
    }

}

<#
 .Synopsis
  Cmdlet to Create required active directory objects.
 
 .Parameter Deploy
  Deployment.
 
 .Parameter Upgrade
  Upgrade.
 
 .Parameter AsHciDeploymentUserCredential
  Deployment credentails.
 
 .Parameter AsHciOUName
  Organizational unit.
 
 .Parameter AsHciPhysicalNodeList
  Physcal machines list.
 
 .Parameter AsHciDeploymentPrefix
  Deployment prefix.
 
 .Parameter AsHciClusterName
  Cluster name.
 
 .Parameter DomainFQDN
  Domain FQDN.
 
 .Parameter DNSServerIP
  DNS server ipaddress,
#>


function New-HciAdObjectsPreCreation
{
    [CmdletBinding(
        DefaultParameterSetName= 'Deploy'
    )]
    Param(
        [Parameter(ParameterSetName = 'Deploy', Mandatory=$true)]
        [Switch]
        $Deploy,

        [Parameter(ParameterSetName = 'Upgrade', Mandatory=$true)]
        [Switch]
        $Upgrade,

        [Parameter(ParameterSetName = 'Deploy', Mandatory=$true)]
        [Parameter(ParameterSetName = 'Upgrade', Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [PSCredential] $AsHciDeploymentUserCredential,

        [Parameter(ParameterSetName = 'Deploy', Mandatory=$true)]
        [Parameter(ParameterSetName = 'Upgrade', Mandatory=$true)]
        [validatePattern('^(ou=[a-z0-9]*\,)')]
        [ValidateNotNullOrEmpty()]
        [string] $AsHciOUName,

        [Parameter(ParameterSetName = 'Deploy', Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string[]] $AsHciPhysicalNodeList,

        [Parameter(ParameterSetName = 'Deploy', Mandatory=$true)]
        [Parameter(ParameterSetName = 'Upgrade', Mandatory=$true)]
        [ValidateLength(1,8)]
        [string] $AsHciDeploymentPrefix,

        [Parameter(ParameterSetName = 'Deploy', Mandatory=$true)]
        [Parameter(ParameterSetName = 'Upgrade', Mandatory=$true)]
        [validatePattern('^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$')]
        [ValidateNotNullOrEmpty()]
        [string] $DomainFQDN,

        [Parameter(ParameterSetName = 'Deploy', Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string] $AsHciClusterName,

        [Parameter(ParameterSetName = 'Deploy', Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
        [string] $DNSServerIP

    )

    # import required modules
    $modules = @('ActiveDirectory','GroupPolicy','Kds')
    foreach ($module in $modules)
    {
        Import-Module -Name $module -Verbose:$false -ErrorAction Stop | Out-Null
    }

    # Test kds root key configuration.
    Test-KDSConfiguration -DomainFQDN $DomainFQDN -Verbose

    $hciParentOUPath = $AsHciOUName.Split(",",2)[1]
    # Test hci parent ou path exists or not."
    Test-AsHciOU -AsHciOUPath $hciParentOUPath -Verbose

    Test-UniqueAdObjects -AsHciDeploymentUserCredential $AsHciDeploymentUserCredential `
                         -AsHciOUName $AsHciOUName `
                         -AsHciPhysicalNodeList $AsHciPhysicalNodeList `
                         -AsHciClusterName $AsHciClusterName `
                         -AsHciDeploymentPrefix $AsHciDeploymentPrefix

    # Create AD objects for a fresh HCI deployment.
    if ($Deploy)
    {
        Test-AsHciDeploymentPrefix -AsHciOUPath $AsHciOUName -AsHciDeploymentPrefix $AsHciDeploymentPrefix -Verbose

        New-AdObjectsForDeployOrUpgrade -Deploy `
                                        -AsHciDeploymentUserCredential $AsHciDeploymentUserCredential `
                                        -AsHciOUName $AsHciOUName `
                                        -AsHciDeploymentPrefix $AsHciDeploymentPrefix `
                                        -DomainFQDN $DomainFQDN `
                                        -AsHciPhysicalNodeList $AsHciPhysicalNodeList `
                                        -AsHciClusterName $AsHciClusterName `
                                        -DNSServerIP $DNSServerIP 
    }
    elseif ($Upgrade)
    {
        Test-AsHciDeploymentPrefix -AsHciOUPath $AsHciOUName -AsHciDeploymentPrefix $AsHciDeploymentPrefix -Verbose

        New-AdObjectsForDeployOrUpgrade -Upgrade `
                                        -AsHciDeploymentUserCredential $AsHciDeploymentUserCredential `
                                        -AsHciOUName $AsHciOUName `
                                        -AsHciDeploymentPrefix $AsHciDeploymentPrefix `
                                        -DomainFQDN $DomainFQDN `
                                        -DNSServerIP $DNSServerIP 
    }
    else
    {
        Write-Error "Invalid operation"
    }
}

Export-ModuleMember -Function New-HciAdObjectsPreCreation
Export-ModuleMember -Function Remove-AsHciOU
# SIG # Begin signature block
# MIInrQYJKoZIhvcNAQcCoIInnjCCJ5oCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCC3lHq9btkj/FjM
# Vooh0dXk2GADRSMS65krWsPPVQAKP6CCDYEwggX/MIID56ADAgECAhMzAAACzI61
# lqa90clOAAAAAALMMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjIwNTEyMjA0NjAxWhcNMjMwNTExMjA0NjAxWjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQCiTbHs68bADvNud97NzcdP0zh0mRr4VpDv68KobjQFybVAuVgiINf9aG2zQtWK
# No6+2X2Ix65KGcBXuZyEi0oBUAAGnIe5O5q/Y0Ij0WwDyMWaVad2Te4r1Eic3HWH
# UfiiNjF0ETHKg3qa7DCyUqwsR9q5SaXuHlYCwM+m59Nl3jKnYnKLLfzhl13wImV9
# DF8N76ANkRyK6BYoc9I6hHF2MCTQYWbQ4fXgzKhgzj4zeabWgfu+ZJCiFLkogvc0
# RVb0x3DtyxMbl/3e45Eu+sn/x6EVwbJZVvtQYcmdGF1yAYht+JnNmWwAxL8MgHMz
# xEcoY1Q1JtstiY3+u3ulGMvhAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUiLhHjTKWzIqVIp+sM2rOHH11rfQw
# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1
# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDcwNTI5MB8GA1UdIwQYMBaAFEhu
# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu
# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w
# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx
# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAeA8D
# sOAHS53MTIHYu8bbXrO6yQtRD6JfyMWeXaLu3Nc8PDnFc1efYq/F3MGx/aiwNbcs
# J2MU7BKNWTP5JQVBA2GNIeR3mScXqnOsv1XqXPvZeISDVWLaBQzceItdIwgo6B13
# vxlkkSYMvB0Dr3Yw7/W9U4Wk5K/RDOnIGvmKqKi3AwyxlV1mpefy729FKaWT7edB
# d3I4+hldMY8sdfDPjWRtJzjMjXZs41OUOwtHccPazjjC7KndzvZHx/0VWL8n0NT/
# 404vftnXKifMZkS4p2sB3oK+6kCcsyWsgS/3eYGw1Fe4MOnin1RhgrW1rHPODJTG
# AUOmW4wc3Q6KKr2zve7sMDZe9tfylonPwhk971rX8qGw6LkrGFv31IJeJSe/aUbG
# dUDPkbrABbVvPElgoj5eP3REqx5jdfkQw7tOdWkhn0jDUh2uQen9Atj3RkJyHuR0
# GUsJVMWFJdkIO/gFwzoOGlHNsmxvpANV86/1qgb1oZXdrURpzJp53MsDaBY/pxOc
# J0Cvg6uWs3kQWgKk5aBzvsX95BzdItHTpVMtVPW4q41XEvbFmUP1n6oL5rdNdrTM
# j/HXMRk1KCksax1Vxo3qv+13cCsZAaQNaIAvt5LvkshZkDZIP//0Hnq7NnWeYR3z
# 4oFiw9N2n3bb9baQWuWPswG0Dq9YT9kb+Cs4qIIwggd6MIIFYqADAgECAgphDpDS
# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0
# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla
# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT
# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB
# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG
# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S
# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz
# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7
# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u
# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33
# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl
# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP
# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB
# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF
# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM
# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ
# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud
# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO
# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0
# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p
# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB
# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw
# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA
# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY
# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj
# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd
# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ
# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf
# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ
# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j
# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B
# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96
# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7
# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I
# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIZgjCCGX4CAQEwgZUwfjELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z
# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAsyOtZamvdHJTgAAAAACzDAN
# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor
# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQg5DvblV9C
# 3jchmKzc5RRO/iFPLSSxBXI3vkk5YC1rUxQwQgYKKwYBBAGCNwIBDDE0MDKgFIAS
# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN
# BgkqhkiG9w0BAQEFAASCAQAfjvXaPfrV0rvZfOfRFLgsMieVugNyaRZpF76eF2DH
# I6INBK8blP6JX7A+TP3aAMKBcZtFNvTs0WOTbRW9iB/tKAKwZ0S5gGHCg1tLWYg7
# I7oYiHOBcrYGrbUaC76RhAyXVULsLnricDmtAubTvXSDGMxdXi96BU+HRBRgnmK7
# ig8RKmofQFQ58daWW6EPmnXzfGC+47eslW8rtnzy2zqYBDOr6EFbkKakpMKb4KGz
# aCYj50ym4gdjE9ZE52LU5Nd3169LCJqt/1rsQWET0/4dEiQubotpwshl7wS4Od6o
# 1WBBmLkAnXaNsFk6vYGQHGOKqKWF3TmOBIh/YX41t8pIoYIXDDCCFwgGCisGAQQB
# gjcDAwExghb4MIIW9AYJKoZIhvcNAQcCoIIW5TCCFuECAQMxDzANBglghkgBZQME
# AgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEEAYRZCgMB
# MDEwDQYJYIZIAWUDBAIBBQAEIJQyb6PJTJMqG9Rp4WvOR/aJs1yZYtPl8tvomrMA
# xrEcAgZjYr41KYEYEzIwMjIxMTExMjA0NDQwLjA4N1owBIACAfSggdSkgdEwgc4x
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1p
# Y3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMg
# VFNTIEVTTjo3ODgwLUUzOTAtODAxNDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt
# U3RhbXAgU2VydmljZaCCEV8wggcQMIIE+KADAgECAhMzAAABqFXwYanMMBhcAAEA
# AAGoMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo
# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y
# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw
# MB4XDTIyMDMwMjE4NTEyM1oXDTIzMDUxMTE4NTEyM1owgc4xCzAJBgNVBAYTAlVT
# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK
# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy
# YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo3ODgw
# LUUzOTAtODAxNDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj
# ZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKPabcrALiXX8pjyXpcM
# N89KTvcmlAiDw4pU+HejZhibUeo/HUy+P9VxWhCX7ogeeKPJ677+LeVdPdG5hTvG
# DgSuo3w+AcmzcXZ2QCGUgUReLUKbmrr06bB0xhvtZwoelhxtPkjJFsbTGtSt+V7E
# 4VCjPdYqQZ/iN0ArXXmgbEfVyCwS+h2uooBhM5UcbPogtr5VpgdzbUM4/rWupmFV
# jPB1asn3+wv7aBCK8j9QUJroY4y1pmZSf0SuGMWY7cm2cvrbdm7XldljqRdHW+CQ
# AB4EqiOqgumfR+aSpo5T75KG0+nsBkjlGSsU1Bi15p4rP88pZnSop73Gem9GWO2G
# RLwP15YEnKsczxhGY+Z8NEa0QwMMiVlksdPU7J5qK9gxAQjOJzqISJzhIwQWtELq
# gJoHwkqTxem3grY7B7DOzQTnQpKWoL0HWR9KqIvaC7i9XlPv+ue89j9e7fmB4nh1
# hulzEJzX6RMU9THJMlbO6OrP3NNEKJW8jipCny8H1fuvSuFfuB7t++KK9g2c2NKu
# 5EzSs1nKNqtl4KO3UzyXLWvTRDO4D5PVQOda0tqjS/AWoUrxKC5ZPlkLE+YPsS5G
# +E/VCgCaghPyBZsHNK7wHlSf/26uhLnKp6XRAIroiEYl/5yW0mShjvnARPr0GIlS
# m0KrqSwCjR5ckWT1sKaEb8w3AgMBAAGjggE2MIIBMjAdBgNVHQ4EFgQUNsfb4+L4
# UutlNh/MxjGkj0kLItUwHwYDVR0jBBgwFoAUn6cVXQBeYl2D9OXSZacbUzUZ6XIw
# XwYDVR0fBFgwVjBUoFKgUIZOaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9w
# cy9jcmwvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3Js
# MGwGCCsGAQUFBwEBBGAwXjBcBggrBgEFBQcwAoZQaHR0cDovL3d3dy5taWNyb3Nv
# ZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENB
# JTIwMjAxMCgxKS5jcnQwDAYDVR0TAQH/BAIwADATBgNVHSUEDDAKBggrBgEFBQcD
# CDANBgkqhkiG9w0BAQsFAAOCAgEAcTuCS2Rqqmf2mPr6OUydhmUx+m6vpEPszWio
# JXbnsRbny62nF9YXTKuSNWH1QFfyc/2N3YTEp4hE8YthYKgDM/HUhUREX3WTwGse
# YuuDeSxWRJWCorAHF1kwQzIKgrUc3G+uVwAmG/EI1ELRExA4ftx0Ehrf59aJm7On
# gn0lTSSiKUeuGA+My6oCi/V8ETxz+eblvQANaltJgGfppuWXYT4jisQKETvoJjBv
# 5x+BA0oEFu7gGaeMDkZjnO5vdf6HeKneILs9ZvwIWkgYQi2ZeozbxglG5YwExoix
# ekxrRTDZwMokIYxXmccscQ0xXmh+I3vo7hV9ZMKTa9Paz5ne4cc8Odw1T+624mB0
# WaW9HAE1hojB6CbfundtV/jwxmdKh15plJXnN1yM7OL924HqAiJisHanpOEJ4Um9
# b3hFUXE2uEJL9aYuIgksVYIq1P29rR4X7lz3uEJH6COkoE6+UcauN6JYFghN9I8J
# RBWAhHX4GQHlngsdftWLLiDZMynlgRCZzkYI24N9cx+D367YwclqNY6CZuAgzwy1
# 2uRYFQasYHYK1hpzyTtuI/A2B8cG+HM6X1jf2d9uARwH6+hLkPtt3/5NBlLXpOl5
# iZyRlBi7iDXkWNa3juGfLAJ3ISDyNh7yu+H4yQYyRs/MVrCkWUJs9EivLKsNJ2B/
# IjNrStYwggdxMIIFWaADAgECAhMzAAAAFcXna54Cm0mZAAAAAAAVMA0GCSqGSIb3
# DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G
# A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIw
# MAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAx
# MDAeFw0yMTA5MzAxODIyMjVaFw0zMDA5MzAxODMyMjVaMHwxCzAJBgNVBAYTAlVT
# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK
# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1l
# LVN0YW1wIFBDQSAyMDEwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA
# 5OGmTOe0ciELeaLL1yR5vQ7VgtP97pwHB9KpbE51yMo1V/YBf2xK4OK9uT4XYDP/
# XE/HZveVU3Fa4n5KWv64NmeFRiMMtY0Tz3cywBAY6GB9alKDRLemjkZrBxTzxXb1
# hlDcwUTIcVxRMTegCjhuje3XD9gmU3w5YQJ6xKr9cmmvHaus9ja+NSZk2pg7uhp7
# M62AW36MEBydUv626GIl3GoPz130/o5Tz9bshVZN7928jaTjkY+yOSxRnOlwaQ3K
# Ni1wjjHINSi947SHJMPgyY9+tVSP3PoFVZhtaDuaRr3tpK56KTesy+uDRedGbsoy
# 1cCGMFxPLOJiss254o2I5JasAUq7vnGpF1tnYN74kpEeHT39IM9zfUGaRnXNxF80
# 3RKJ1v2lIH1+/NmeRd+2ci/bfV+AutuqfjbsNkz2K26oElHovwUDo9Fzpk03dJQc
# NIIP8BDyt0cY7afomXw/TNuvXsLz1dhzPUNOwTM5TI4CvEJoLhDqhFFG4tG9ahha
# YQFzymeiXtcodgLiMxhy16cg8ML6EgrXY28MyTZki1ugpoMhXV8wdJGUlNi5UPkL
# iWHzNgY1GIRH29wb0f2y1BzFa/ZcUlFdEtsluq9QBXpsxREdcu+N+VLEhReTwDwV
# 2xo3xwgVGD94q0W29R6HXtqPnhZyacaue7e3PmriLq0CAwEAAaOCAd0wggHZMBIG
# CSsGAQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGCNxUCBBYEFCqnUv5kxJq+gpE8RjUp
# zxD/LwTuMB0GA1UdDgQWBBSfpxVdAF5iXYP05dJlpxtTNRnpcjBcBgNVHSAEVTBT
# MFEGDCsGAQQBgjdMg30BATBBMD8GCCsGAQUFBwIBFjNodHRwOi8vd3d3Lm1pY3Jv
# c29mdC5jb20vcGtpb3BzL0RvY3MvUmVwb3NpdG9yeS5odG0wEwYDVR0lBAwwCgYI
# KwYBBQUHAwgwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGG
# MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZWy4/oolxiaNE9lJBb186a
# GMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3Br
# aS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3JsMFoGCCsG
# AQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29t
# L3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcnQwDQYJKoZIhvcN
# AQELBQADggIBAJ1VffwqreEsH2cBMSRb4Z5yS/ypb+pcFLY+TkdkeLEGk5c9MTO1
# OdfCcTY/2mRsfNB1OW27DzHkwo/7bNGhlBgi7ulmZzpTTd2YurYeeNg2LpypglYA
# A7AFvonoaeC6Ce5732pvvinLbtg/SHUB2RjebYIM9W0jVOR4U3UkV7ndn/OOPcbz
# aN9l9qRWqveVtihVJ9AkvUCgvxm2EhIRXT0n4ECWOKz3+SmJw7wXsFSFQrP8DJ6L
# GYnn8AtqgcKBGUIZUnWKNsIdw2FzLixre24/LAl4FOmRsqlb30mjdAy87JGA0j3m
# Sj5mO0+7hvoyGtmW9I/2kQH2zsZ0/fZMcm8Qq3UwxTSwethQ/gpY3UA8x1RtnWN0
# SCyxTkctwRQEcb9k+SS+c23Kjgm9swFXSVRk2XPXfx5bRAGOWhmRaw2fpCjcZxko
# JLo4S5pu+yFUa2pFEUep8beuyOiJXk+d0tBMdrVXVAmxaQFEfnyhYWxz/gq77EFm
# PWn9y8FBSX5+k77L+DvktxW/tM4+pTFRhLy/AsGConsXHRWJjXD+57XQKBqJC482
# 2rpM+Zv/Cuk0+CQ1ZyvgDbjmjJnW4SLq8CdCPSWU5nR0W2rRnj7tfqAxM328y+l7
# vzhwRNGQ8cirOoo6CGJ/2XBjU02N7oJtpQUQwXEGahC0HVUzWLOhcGbyoYIC0jCC
# AjsCAQEwgfyhgdSkgdEwgc4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5n
# dG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9y
# YXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNv
# MSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo3ODgwLUUzOTAtODAxNDElMCMGA1UE
# AxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsOAwIaAxUA
# bLr8xJ9BB4rL4Yg58X1LZ5iQdyyggYMwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEG
# A1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWlj
# cm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFt
# cCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIFAOcZGfkwIhgPMjAyMjExMTEyMjU5
# MDVaGA8yMDIyMTExMjIyNTkwNVowdzA9BgorBgEEAYRZCgQBMS8wLTAKAgUA5xkZ
# +QIBADAKAgEAAgIGEAIB/zAHAgEAAgISNDAKAgUA5xpreQIBADA2BgorBgEEAYRZ
# CgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0G
# CSqGSIb3DQEBBQUAA4GBADOSBWdPuDeIN6e+mFDW8Q8q5GD/YfBtmaEFWAP+kjIn
# 9LoUEbZjPUInJDJ7np9AHyKyShC4Zy8OCou6/Dg93bte+Zb5ImivGewIr3B8dgtP
# AT9AoUkH680TGgOPewxHcIGhfKgFeak2eblhrYHHCh9P7qymMwKumEHLXMrwa3UE
# MYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0
# b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh
# dGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMA
# AAGoVfBhqcwwGFwAAQAAAagwDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJ
# AzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQg6hZpkyNMMyf+Twv3oj5t
# 6PZKwORqvK7tKLWm+OsRBRQwgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCB0
# /ssdAMsHwnNwhfFBXPlFnRvWhHqSX9YLUxBDl1xlpjCBmDCBgKR+MHwxCzAJBgNV
# BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w
# HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29m
# dCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABqFXwYanMMBhcAAEAAAGoMCIEIL1z
# XdN5qBTZmtT/ZJaiCv1k6sEsGoZU1la8O93VdKWhMA0GCSqGSIb3DQEBCwUABIIC
# AJ1QydtQs+eCH6AKReznydkbE26R+lTFKxreVA0RzUlo+Wj4W1NZu3mdOEnDtx/a
# SCcdj3n81U0Ky9bJ+xLHRk1ifZtfuwrcshieB5WJTNVue+0hwy8CvvHfJXIiRxo3
# K6eEm+vTHrCy5kzT6Cz3fsuC9Fv0IrTHobHIMJOJRD2aul+0tLC+0ADE0qrNcMBg
# J7O5GfOrB2O/Vt0Kgzh3upGUfMcQVYQmU2K3+69D7aPJiCWDcfhEDhFqP8ZjdvbB
# 9qGBzKl0j6xb7Uw09M7wL2dp+zZI/QaLqAk0k7AQPnGse4HbL9a8bh1EmUAeZ7eT
# JU7KME8K6AEvyys0pPKdxFs0K+qsJ5+4XhZj2GKcIQ2KreCa1LWT737lGGk5x71N
# YYKiGfjL0q8Y06NcWQ9ikg7f4L/BzIBZUdG1yXND6BIqVdzLuG/EFDiLdkDsvfki
# KGLymSplXV64CWroYgCFDlPNK4cVzpkmZEtjWA7APmsToCptY3WJ+aGR0xfxv0fU
# BGQNxH4bLFYwSPIz8QMNaEX/muLjrdpgSS0Vab8dRk7uWBG+Ucx2k0MZl6R5kFe7
# IoFUGBrmn3esm2qJWIqxah23eLDzj5X13XZkLwjd+JtAzD43zac9MUWG10TBN7wW
# hQzOYioCbQE/j4g78bDZti7ToeqX40CPS1Cwragh5Ubo
# SIG # End signature block