Export/New-ScaleSetImage.ps1

function Global:New-ScaleSetImage {
    [CmdletBinding()]
    <#
    .SYNOPSIS
        Creates a new Application Server, installs Business Central, sys-preps it and saves it as an image
    .DESCRIPTION
        This CmdLet will create a new VM, initialize it (install this and other modules to it), download the desired Business Central DVD and install it locally.
        Afterwards it will generalize the created VM and set it as an Image to be used for future ScaleSets
    #>

    param(        
        [Parameter(Mandatory = $true)]
        [string]
        $ResourceGroupName,        
        [Parameter(Mandatory = $true)]
        [string]
        $ResourceLocation,
        [Parameter(Mandatory = $true)]
        [string]
        $Name,
        [Alias("Version")]
        [Parameter(Mandatory = $false)]
        [string]
        $BCVersion,
        [Alias("CumulativeUpdate")]
        [Parameter(Mandatory = $false)]
        [string]
        $BCCumulativeUpdate,
        [Alias("Language")]
        [Parameter(Mandatory = $false)]
        [string]
        $BCLanguage,
        [ValidateSet('App', 'Web')]
        [string]
        $InstallationType = "App",
        [Parameter(Mandatory = $false)]
        [string]
        $DownloadDirectory,
        [Parameter(Mandatory = $false)]
        [string]
        $ConfigurationFile,
        [Parameter(Mandatory = $false)]
        [string]
        $LicenseFilename,
        [Alias("VirtualNetworkName")]
        [Parameter(Mandatory = $true)]
        [string]
        $VNetName,
        [Parameter(Mandatory = $true)]
        [string]
        $VMName,
        [Parameter(Mandatory = $true)]
        [string]
        $VMImagePublisher,
        [Parameter(Mandatory = $true)]
        [string]
        $VMImageOffer,
        [Parameter(Mandatory = $true)]
        [string]
        $VMImageSku,
        [Parameter(Mandatory = $true)]
        [string]
        $VMSize,
        [Parameter(Mandatory = $true)]
        [PSCredential]
        $VMCredentials,
        [Parameter(Mandatory = $true)]
        [int]
        $VMDiskSizeInGb,
        [Parameter(Mandatory = $true)]
        [string]
        $VMStorageAccountType,
        [Parameter(Mandatory = $true)]
        [string]
        $VMDataDiskName,
        [Alias("NetworkInterfaceName")]
        [Parameter(Mandatory = $true)]
        [string]
        $NicName,
        [Alias("NetworkInterfacePrivateIP")]
        [Parameter(Mandatory = $false)]
        [string]
        $NicPrivateIP,
        [Parameter(Mandatory = $false)]
        [string]
        $SubnetName,
        [Alias("PublicIPName")]
        [Parameter(Mandatory = $false)]
        [string]
        $PipName,
        [Alias("PublicIPDomainNameLabel")]
        [Parameter(Mandatory = $false)]
        [string]
        $PipDnsLabel,
        [Parameter(Mandatory = $true)]
        [string]
        $LocalPropScaleSetName,
        [Parameter(Mandatory = $true)]
        [string]
        $LocalPropStorageAccountName,
        [Parameter(Mandatory = $true)]
        [string]
        $LocalPropKeyVaultName,
        [Parameter(Mandatory = $true)]
        [string]
        $LocalPropStorageTableNameSetup,
        [Parameter(Mandatory = $true)]
        [string]
        $LocalPropStorageTableNameEnvironments,
        [Parameter(Mandatory = $true)]
        [string]
        $LocalPropStorageTableNameEnvironmentDefaults,        
        [Parameter(Mandatory = $true)]
        [string]
        $LocalPropStorageTableNameInfrastructureData,
        [Parameter(Mandatory = $false)]
        [string]
        $GalleryName,
        [Parameter(Mandatory = $false)]
        [string]
        $GalleryDefinitionName,
        [Parameter(Mandatory = $false)]
        [string]
        $GalleryPublisher,
        [Parameter(Mandatory = $false)]
        [string]
        $GalleryOffer,
        [Parameter(Mandatory = $false)]
        [string]
        $GallerySku,
        [HashTable]
        $Tags,
        [switch]
        $AsJob
    )
    process {
        
        $image = Get-AzImage -ResourceGroupName $ResourceGroupName -ImageName $Name -ErrorAction SilentlyContinue
        if ($image) {                        
            Write-CustomHost -Message "Image $Name already exists."
            return
        }
        Write-CustomHost -Message "Starting Image creation for $Name..."

        $scriptBlock = {
            # Create Variables in Scope of ScriptBlock for all variables passed in ArgumentList
            $args[0].GetEnumerator() | ForEach-Object {
                New-Variable -Name $_.Key -Value $_.Value
            }
            # Copy arguments for NIC creation from parent call
            $VMArguments = @{
                ResourceGroupName    = $ResourceGroupName
                ResourceLocation     = $ResourceLocation
                Name                 = $Name
                VNetName             = $VNetName
                VMName               = $VMName
                VMImagePublisher     = $VMImagePublisher
                VMImageOffer         = $VMImageOffer
                VMImageSku           = $VMImageSku
                VMSize               = $VMSize
                VMCredentials        = $VMCredentials
                VMDiskSizeInGb       = $VMDiskSizeInGb
                VMStorageAccountType = $VMStorageAccountType
                VMDataDiskName       = $VMDataDiskName
                NicName              = $NicName
                NicPrivateIP         = $NicPrivateIP
                SubnetName           = $SubnetName
                PipName              = $PipName
                PipDnsLabel          = $PipDnsLabel
                Tags                 = $Tags
            }
            <#
            foreach ($param in $PsBoundParameters.GetEnumerator() | Where-Object { ($_.Key -notlike "BC*" ) -and ($_.Key -notlike "LocalProp*" ) -and ($_.Key -notlike "InstallationType*" ) -and ($_.Key -notlike "DownloadDirectory*" ) -and ($_.Key -notlike "ConfigurationFile*" ) -and ($_.Key -notlike "LicenseFilename*" ) }) {
             
                $VMArguments.Add($param.Key, $param.Value)
            }
            #>

            <#
            Write-Host "Test1"
            $VMArguments | Format-Table
            Write-Host "Test2"
            return
            #>

            # Create new VM (as Application Server), might be a job, might be a VM-object (if VM already exists)
            #$vm = New-CustomAzVm @VMArguments -AsJob:$AsJob
            $vm = New-CustomAzVm @VMArguments
            if ($vm.GetType().ToString() -eq "System.Management.Automation.Job") {
                # Means: Job is currently running / VM is currently being deployed
                Write-CustomHost -Message "Receiving job..."
                $vm = Receive-Job -Job $vm -Wait
                Write-CustomHost -Message "Job received"
                $vmJustCreated = $true
            }
            
            # Assign created VM the Contributor role for the resource group (to be able to read data from storage account)
            # TODO: Check if lesser role is a possibility
            # TODO: Check if role is needed here anyway (maybe only for Scale Set relevant)
            $vm = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VMName -ErrorAction Stop
            if (-not(Get-AzRoleAssignment -ObjectId $vm.Identity.PrincipalId -ResourceGroupName $ResourceGroupName -RoleDefinitionName Contributor -ErrorAction SilentlyContinue)) {
                Write-CustomHost -Message "Assigning access role to managed identity of VM..."
                New-AzRoleAssignment -ObjectId $vm.Identity.PrincipalId -RoleDefinitionName Contributor -ResourceGroupName $ResourceGroupName | Out-Null
                Write-CustomHost -Message "Done."
            }
            
            if ($vmJustCreated) {
                # TODO: Check if there is a way to find out if network connection is already available (to not sleep that long)
                # Hint: Sometimes the installation of the module during command InstallD365Module failed because it couldn't connect to gallery
                # Waiting some more minutes solved it
                
                Write-CustomHost -Message "Sleeping for 2 minutes to give the VM a chance to completely start (network might be unavailable if we start right away)..."
                Start-Sleep -Seconds (2 * 60)
                Write-CustomHost -Message "Done sleeping."
                
                $scriptExecutionParams = @{
                    ResourceGroupName = $ResourceGroupName
                    ResourceLocation  = $ResourceLocation
                    VMName            = $VMName
                }
                #Submit-ScriptToVmAndExecute @scriptExecutionParams -ScriptBlockName WaitForNetwork -MsgBeforeExecuting "Waiting for network being available..."
            }
        
            $vmPreparationParams = @{
                ResourceGroupName                            = $ResourceGroupName
                ResourceLocation                             = $ResourceLocation
                VMName                                       = $VMName
                LocalPropScaleSetName                        = $LocalPropScaleSetName
                LocalPropStorageAccountName                  = $LocalPropStorageAccountName
                LocalPropKeyVaultName                        = $LocalPropKeyVaultName
                LocalPropStorageTableNameSetup               = $LocalPropStorageTableNameSetup
                LocalPropStorageTableNameEnvironments        = $LocalPropStorageTableNameEnvironments
                LocalPropStorageTableNameEnvironmentDefaults = $LocalPropStorageTableNameEnvironmentDefaults
                LocalPropStorageTableNameInfrastructureData  = $LocalPropStorageTableNameInfrastructureData
                Version                                      = $BCVersion
                CumulativeUpdate                             = $BCCumulativeUpdate
                Language                                     = $BCLanguage
                InstallationType                             = $InstallationType
                DownloadDirectory                            = $DownloadDirectory
                ConfigurationFile                            = $ConfigurationFile
                LicenseFilename                              = $LicenseFilename
            }
            <#
            if (-not([string]::IsNullOrEmpty($InstallationType))) {
                $vmPreparationParams.Add('InstallationType', $InstallationType)
            }
            if (-not([string]::IsNullOrEmpty($DownloadDirectory))) {
                $vmPreparationParams.Add('DownloadDirectory', $DownloadDirectory)
            }
            if (-not([string]::IsNullOrEmpty($ConfigurationFile))) {
                $vmPreparationParams.Add('ConfigurationFile', $ConfigurationFile)
            }
            if (-not([string]::IsNullOrEmpty($LicenseFilename))) {
                $vmPreparationParams.Add('LicenseFilename', $LicenseFilename)
            }
            #>

            Set-VMPreparation @vmPreparationParams        
        
            Write-CustomHost -Message "Deallocating VM..."
            Stop-AzVm -ResourceGroupName $ResourceGroupName -Name $VMName -Force | Out-Null
            Write-CustomHost -Message "Generalizing VM..."
            Set-AzVM -ResourceGroupName $ResourceGroupName -Name $VMName -Generalized | Out-Null
        
            Write-CustomHost -Message "Preparing VM-image..."
            $vm = Get-AzVM -Name $VMName -ResourceGroupName $ResourceGroupName
            $image = New-AzImageConfig -Location $ResourceLocation -SourceVirtualMachineId $vm.ID
            New-AzImage -Image $image -ImageName $Name -ResourceGroupName $ResourceGroupName | Out-Null
            Write-CustomHost -Message "Done."
        
            Clear-ScaleSetPreparationResources -ResourceGroupName $ResourceGroupName -Tag $tags

            if (-not([string]::IsNullOrEmpty($GalleryName))) {
                Write-CustomHost -Message "Adding Image to Gallery..."
                Add-CustomImageToGallery -ResourceGroupName $ResourceGroupName -ResourceLocation $ResourceLocation `
                    -GalleryName $GalleryName -GalleryDefinitionName $GalleryDefinitionName -GalleryPublisher $GalleryPublisher `
                    -GalleryOffer $GalleryOffer -GallerySku $GallerySku -SourceImageName $Name
            }
        }

        # Get all parameters from within this function call
        $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-ScaleSetImage