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 VMCredentials = $VMCredentials } <# 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 return # TODO: Remove again 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 |