Export/New-CustomAzVmss.ps1

# Will be called in VM
function Global:New-CustomAzVmss {
    [CmdletBinding()]
    <#
    .SYNOPSIS
        ...
    .DESCRIPTION
        ...
    #>

    param(
        [Parameter(Mandatory = $true)]
        [string]
        $ResourceGroupName,        
        [Parameter(Mandatory = $true)]
        [string]
        $ResourceLocation,
        [Parameter(Mandatory = $true)]
        [string]
        $Name,
        [Parameter(Mandatory = $true)]
        [int]
        $NoOfInstances,
        [Parameter(Mandatory = $true)]
        [string]
        $BaseImageName,
        [Parameter(Mandatory = $true)]
        [string]
        $VMSize,
        [Parameter(Mandatory = $true)]
        [PSCredential]
        $VMCredentials,
        [Parameter(Mandatory = $true)]
        [string]
        $ComputerNamePrefix,
        [Parameter(Mandatory = $true)]
        [string]
        $NetworkIPConfigName,
        [Parameter(Mandatory = $true)]
        [string]
        $NetworkInterfaceConfigName,        
        [Parameter(Mandatory = $true)]
        [string]
        $LoadBalancerName,
        [Parameter(Mandatory = $true)]
        [string]
        $FrontendIpConfigName,
        [Parameter(Mandatory = $true)]
        [string]
        $VirtualNetworkName,
        [Parameter(Mandatory = $true)]
        [string]
        $SubnetName,
        [Parameter(Mandatory = $false)]
        [string]
        $SubnetAddressPrefix,
        [Parameter(Mandatory = $true)]
        [switch]
        $CreatePublicIP,
        [Parameter(Mandatory = $true)]
        [string]
        $BackendAddressPoolName,
        #[Parameter(Mandatory = $true)]
        #[string]
        #$AdminUsername,
        #[Parameter(Mandatory = $true)]
        #[string]
        #$AdminPassword,
        [Parameter(Mandatory = $true)]
        [string]
        $KeyVaultName,
        [switch]
        $AsJob
    )
    process {
        # TODO: Check if credentials should be taken from KeyVault instead of passing them as parameter
        # TODO: Check if Domain Join can be done with xxx
        # see: https://docs.microsoft.com/en-us/powershell/module/az.compute/set-azvmssosprofile?view=azps-2.8.0
        # https://docs.microsoft.com/en-us/windows-hardware/customize/desktop/unattend/microsoft-windows-unattendedjoin-identification-joindomain

        $VMSS = Get-AzVmss -ResourceGroupName $ResourceGroupName -VMScaleSetName $Name -ErrorAction SilentlyContinue
        if ($VMSS) {
            Write-CustomHost -Message "Scale Set $Name already exists."
            return
        }
        Write-CustomHost -Message "Creating Scale Set $Name ..."
        # TODO: Add handling for Subnet-creation (if not existing)
        $scriptBlock = {
            # Create Variables in Scope of ScriptBlock for all variables passed in ArgumentList
            $args[0].GetEnumerator() | ForEach-Object {
                New-Variable -Name $_.Key -Value $_.Value
            }
            #Write-CustomHost -Message "Creating Scale Set $Name..."
            $VNet = Get-AzVirtualNetwork -ResourceGroupName $ResourceGroupName -Name $VirtualNetworkName -ErrorAction SilentlyContinue
            $VMSSIPCfg = New-AzVmssIPConfig -Name $NetworkIPConfigName -SubnetId $VNet.Subnets[0].Id

            <#
            # DomainJoin-extension settings
            $Settings = @{
                "Name" = "bctest.local";
                "User" = "bctest.local\$AdminUsername";
                "Restart" = "true";
                "Options" = 3;
                #"OUPath" = "OU=TEST,OU=My Computers,DC=yourdomain,DC=onmicrosoft,DC=com"
            }
         
            $ProtectedSettings = @{
                    "Password" = $AdminPassword
            }
            #
            | Set-AzVmssOSProfile -ComputerNamePrefix $ComputerNamePrefix -AdminUsername $AdminUsername -AdminPassword $AdminPassword `
            #>
 
            $adminUserName = (Get-AzKeyVaultSecret -VaultName $KeyVaultName -Name 'DomainAdminUsername').SecretValueText
            $adminUserPass = (Get-AzKeyVaultSecret -VaultName $KeyVaultName -Name 'DomainAdminPassword').SecretValueText

            # TODO: Check if its possible to start with 0 instances and update later
            # Last time KeyVault assignment failed
            $NoOfInstances = 0

            $domainJoinSettings = Get-DomainJoinExtensionSettings -KeyVaultName $KeyVaultName
            $VMSS = New-AzVmssConfig -Location $ResourceLocation -SkuCapacity $NoOfInstances -SkuName $VMSize -UpgradePolicyMode "Automatic" -IdentityType SystemAssigned `
            | Add-AzVmssNetworkInterfaceConfiguration -Name $NetworkInterfaceConfigName -Primary $True -IPConfiguration $VMSSIPCfg `
            | Add-AzVmssExtension -Publisher $domainJoinSettings.Publisher -Type $domainJoinSettings.Type  -TypeHandlerVersion $domainJoinSettings.TypeHandlerVersion -Name $domainJoinSettings.Name -Setting $domainJoinSettings.Settings -ProtectedSetting $domainJoinSettings.ProtectedSetting -AutoUpgradeMinorVersion $true `
            | Set-AzVmssOSProfile -ComputerNamePrefix $ComputerNamePrefix  -AdminUsername $adminUserName -AdminPassword $adminUserPass `
            | Set-AzVmssStorageProfile  -OsDiskCreateOption 'FromImage' -OsDiskCaching "None" `
                -ImageReferenceId (Get-AzImage -ImageName $BaseImageName -ResourceGroupName $resourceGroupName).Id

            $VMSS = New-AzVmss -ResourceGroupName $ResourceGroupName -Name $Name -VirtualMachineScaleSet $VMSS
            Write-CustomHost -Message "Done."
            
            # TODO: Check if Role "Contributor" is necessary
            $VMSS = Get-AzVmss -ResourceGroupName $ResourceGroupName -VMScaleSetName $Name
            $RoleDefinitionName = "Contributor"
            if (-not(Get-AzRoleAssignment -ObjectId $VMSS.Identity.PrincipalId -ResourceGroupName $ResourceGroupName -RoleDefinitionName $RoleDefinitionName -ErrorAction SilentlyContinue)) {
                Write-CustomHost -Message "Assigning access role to managed identity of VM Scale Set..."
                New-AzRoleAssignment -ObjectId $VMSS.Identity.PrincipalId -RoleDefinitionName $RoleDefinitionName -ResourceGroupName $ResourceGroupName | Out-Null
                Write-CustomHost -Message "Done."
            }
            # The assignment of the managed identity takes some time. When we create the Scale Set with 0 instances in the beginning
            # we need to wait a moment. Otherwise the creation of the instances takes already enough time to not have to wait
            if ($NoOfInstances -eq 0) {
                Write-CustomHost -Message "Waiting for 30 seconds, so that the managed identity is available..."
                Start-Sleep -Seconds 30 
            }
            Set-KeyVaultPermissionsForScaleSet -ResourceGroupName $ResourceGroupName -KeyVaultName $KeyVaultName -ScaleSetName $Name
        }

        # Get all parameters from within this function call and pass to actual ScriptBlock
        $params = Get-FunctionParameters $MyInvocation
        if ($AsJob) {            
            Start-Job -ScriptBlock $scriptBlock -InitializationScript { Import-Module SetupD365Environment -Force } -ArgumentList $params
        }
        else {
            Invoke-Command -ScriptBlock $scriptBlock -ArgumentList $params
        }
    }
}
Export-ModuleMember -Function New-CustomAzVmss