functions/public/New-SMBAzureDeployment.ps1

function New-SMBAzureDeployment {
    [cmdletbinding(DefaultParameterSetName = "AzureTenantDomain")]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string] $Location,
        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string] $AutomationLocation,
        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string] $LogAnalyticsLocation,
        [parameter()]
        [switch] $AsJob,
        [parameter(Mandatory = $true)]
        [ValidateLength(1,15)]
        [string] $CustomerName,
        [parameter(Mandatory = $true)]
        [ValidateSet('small', 'medium', 'large')]
        [string] $CustomerSize = 'small',
        [parameter()]
        [ValidateSet('none', 'small', 'medium')]
        [string] $AdditionalVMSize = 'none',
        [parameter()]
        [ValidateSet('none', 'small')]
        [string] $AdditionalSQLInstanceSize = 'none',
        [ValidateSet('none', 'standard')]
        [string] $Backup = 'none',
        [ValidateSet('none', 'basic')]
        [string] $VPN = 'none',
        [parameter()]
        [ValidateNotNullOrEmpty()]
        [ValidateSet('free')]
        [string] $Management = 'free',
        [parameter()]
        [ValidateSet('2012R2', '2016')]
        [string] $OS = '2012R2',
        [parameter()]
        [string] $SysAdminPassword = $(New-SWRandomPassword),
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [pscredential] $Credential = (Get-Credential -Message "Please provide your Partner Credentials"),
        [parameter(ParameterSetName = "AzureTenantId", Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string] $TenantId,
        [parameter(ParameterSetName = "AzureTenantDomain", Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string] $TenantDomain,
        [parameter()]
        [ValidateNotNullOrEmpty()]
        [string] $SubscriptionId,
        [parameter()]
        [ValidateNotNullOrEmpty()]
        [string] $SubscriptionName,
        [Parameter(DontShow = $true)]
        [ValidateNotNullOrEmpty()]
        [string] $ResourceGroupPrefix = "smb_rg_",
        [Parameter()]
        [switch] $NoUpdateCheck,
        [Parameter()]
        [ValidateSet('Standard_LRS', "Standard_ZRS", "Standard_GRS", "Standard_RAGRS", "Premium_LRS")]
        [string] $StorageType = "Standard_LRS",
        [Parameter(DontShow = $true)]
        [string] $InstanceId
    )
    
    begin {
        $Continue = $true
        $Management = 'free'
        if(!$LogAnalyticsLocation){
            $LogAnalyticsLocation = $Location
        }
        if(!$AutomationLocation){
            $AutomationLocation = $Location
        }
        $Log = $null
        if ($PSBoundParameters.ContainsKey('InstanceId')) {
            $SyncHash = Get-JobVariable -Id $InstanceId
            $Log = $SyncHash.log
        }
        else {
            $SyncHash = Get-JobVariable
            $Log = Start-Log -InstanceId $SyncHash.InstanceId
            $SyncHash.Log = $Log
        }
        $PSDefaultParameterValues = @{"Write-Log:Log" = $Log}
        if (!$PSBoundParameters.ContainsKey('NoUpdateCheck')) {
            Test-ModuleVersion -ModuleName "SMBBluePrint"
        }

        $CustomerNamePrefix = [Regex]::Replace($CustomerName, '[^a-zA-Z0-9]', '')
        $ResourceGroupName = "$ResourceGroupPrefix$CustomerNamePrefix"
        $SecurePassword = $SysAdminPassword|ConvertTo-SecureString -AsPlainText -Force
        write-log -Message "Using $CustomerNamePrefix as resource naming prefix"
        Write-Log -Message "Using $ResourceGroupName as target resource group"
        $ActiveSubscription = ""
        if ($Credential) {
            try {
                Connect-Cloud -Credential $Credential
                $Tenant = Get-Tenant -TenantDomain $TenantDomain -TenantId $TenantId
                if ($Tenant.Default -eq $false) {
                    $null = Add-AzureRMAccount -Credential $Credential -TenantId $TenantId
                }
                else {
                    $null = Add-AzureRMAccount -Credential $Credential
                }

                if ($SubscriptionId) {
                    $null = Select-AzureRmSubscription -SubscriptionId $SubscriptionId
                }
                elseif ($SubscriptionName) {
                    $null = Select-AzureRmSubscription -SubscriptionName $SubscriptionName
                }
                else {
                    # use default subscription
                }
            } catch {
                write-log -type error -message "Error during Azure connection: $_"
            }
        }
        try {
            $null = Get-AzureRmContext
        }
        catch {
            Write-Error "No active Azure subscription is present in the session. Please use Login-AzureRMAccount and Select-AzureRMSubscription to set the target subscription, or specify Tenant/Credential information"
            return $null
        }
        if ((Get-AzureRmResourceGroup -Name $ResourceGroupName -ErrorAction Ignore)) {
            Write-Log -Type Error -Message "Resource group already exists, please choose another customer name"
            return
        }
        if ((Test-AzureRmDnsAvailability -DomainNameLabel $CustomerNamePrefix.ToLower() -Location "westeurope") -eq $false) {
            write-log -type error -Message "Domain Name already taken, please choose another customer name"
            return
        }
        if ($CustomerNamePrefix -like "*microsoft*") {
            write-log -type error -message "'Microsoft' can not be a part of the customer name, please choose another customer name"
            return
        }
        if ((Test-AADPasswordComplexity -MinimumLength 12 -Password $SysAdminPassword) -eq $false) {
            write-log -type error -message "Password does not meet complexity requirements"
            return
        }
        Write-Log -Message "Checking resource availability"
        $CompatibilityResults = Test-AzureResourceLocation -Location $Location -ResourceFile "$Root\resources"
        Write-Log -Message "Incompatible resources: $($CompatibilityResults.Count)"

        $Count = $CompatibilityResults.Count
        
        if ($Count -gt 0) {
            $Continue = $false
            foreach ($Result in $CompatibilityResults) {
                Write-Log -Message "Incompatible resource: $($Result["Resource"])"
                switch -regex ($Result["Resource"]) {
                    
                    "microsoft\.(operationalinsights|operationsmanagement)" {
                        
                       
                            if($LogAnalyticsLocation -in $Result["AvailableLocations"]) {
                                $Continue = $true
                                break
                            }
                            
                            $choices = $Result["AvailableLocations"]
                            $params = @{
                                Title = "Log Analytics Features Unsupported for this Region"
                                Message = "The selected Azure Region does not support the Log Analytics features of the SMB Blueprint solution. In which region should they be deployed instead (by re-running this command with the -LogAnalyticsLocation Parameter you can automatically deploy non-compatible resources to a fallback region)?" 
                                Choices = $choices
                            }
                            $answer = Read-Choice @params
                           
                            write-log -message "You chose: $answer"
                            switch ($answer) {
                                "Cancel" {
                                    write-log -message "Deployment Cancelled";
                                    $Continue = $false
                                }
                                default {
                                    
                                    $LogAnalyticsLocation = $answer
                                    write-log -message "Continuing Deployment with Log Analytics Location: $LogAnalyticsLocation"
                                    $Continue = $true
                                }
                            }
                       
                    }



                    "microsoft\.automation" {
                        
                        if ($AutomationLocation -in $Result["AvailableLocations"]) {
                            $Continue = $true
                            break
                        }
                            
                            $choices = $Result["AvailableLocations"]
                            $params = @{
                                Title = "Automation Features Unsupported for this Region"
                                Message = "The selected Azure Region does not support the Automation features of the SMB Blueprint solution. In which region should they be deployed instead (by re-running this command with the -AutomationLocation Parameter you can automatically deploy non-compatible resources to a fallback region)?" 
                                Choices = $choices
                            }
                            $answer = Read-Choice @params
                           
                            write-log -message "You chose: $answer"
                            switch ($answer) {
                                "Cancel" {
                                    write-log -message "Deployment Cancelled"
                                    $Continue = $false
                                }
                                default {
                                    
                                    $AutomationLocation = $answer
                                    write-log -message "Continuing Deployment with Automation Location: $AutomationLocation"
                                    $Continue = $true
                                }
                            }
                        }

                    "microsoft.recoveryservices" {
                        write-log -type warning -message "Backup is not supported at this location. The feature will not be deployed"
                        $Backup = "none"
                    }
                }
            }
        }
        else {
            # do nothing
        }
        $AzureParameters = @{
            customername = $CustomerNamePrefix
            customersize = $CustomerSize
            sql = $AdditionalSQLInstanceSize
            vm = $AdditionalVMSize
            backupEnabled = $Backup
            vpnGateway = $VPN
            scheduleid01 = $([guid]::NewGuid().ToString())
            scheduleid02 = $([guid]::NewGuid().ToString())
            scheduleStartDate = (get-date).AddDays(1).ToString("yyyy/MM/dd") 
            managementEnabled = $Management
            logAnalyticsLocation = $LogAnalyticsLocation
            automationLocation = $AutomationLocation
            OSVersion = $OS
            storageType = $StorageType
        }
        $AzureParameters.Add('adminPassword', $SecurePassword)
        
        
        
    }
    process {
        if (!$Continue) {
            Write-Log "Deployment aborted"
            return
        }
        Write-Log "Creating Resourcegroup '$($ResourceGroupName)'"
        try {
            $null = New-AzureRmResourceGroup -Name $ResourceGroupName -Location $Location
        }
        catch {
            Write-log -Type Error -Message "Error while deploying resource group: $_"
            return $null
        }

        # Write-Log "Creating Service Principal"
        # Try {
        # $spnId = New-AzureServicePrincipal -ApplicationDisplayName $ResourceGroupName.Replace("_rg_","_spn_") -ResourceGroup $ResourceGroupName -SubscriptionId $SubscriptionId -Password $SysAdminPassword
        # $AzureParameters.add('SPNId',$spnId)
        # } catch {
        # Write-Log -Type Error -Message "Error while deploying SPN: $_"
        # }

        write-log "Deploying solution to resource group using template url $($global:templateurl)"
        try {
            # if((Get-Variable -name 'SyncHash' -ErrorAction SilentlyContinue) -eq $null){
            #write-log -message "Running in CLI mode"
            # $SyncHash = [hashtable]::Synchronized(@{})
            # $SyncHash.Root = $global:root
            # $SyncHash.Log = $Log
            #} else {
            #write-log -message "Running in GUI mode"
            #}

            $SyncHash.ResourceGroupName = $ResourceGroupName
            $SyncHash.DeploymentParameters = $AzureParameters
            $SyncHash.Credential = $Credential
            $SyncHash.TenantId = $TenantId
            $SyncHash.SubscriptionId = $SubscriptionId
            $CredentialGuid = $SyncHash.InstanceId
            $CredentialDirectory = "$env:APPDATA\SMBBlueprint\credentials"
            #$null = new-item -Path $CredentialDirectory -ItemType Directory -Force
            # while(!(test-path "$CredentialDirectory\SBSDeployment-$($SyncHash.InstanceId).json")){
            # $null = Save-AzureRmProfile -path "$CredentialDirectory\SBSDeployment-$($SyncHash.InstanceId).json" -Force
            #}

            $SyncHash.DeploymentStart = get-date
            $SyncHash.DeploymentJob = new-object psobject -Property @{
                Type = 'Azure'
                Duration = "00:00:00"
                Status = @{
                    Deployment = @()
                    Configuration = @{
                        Domain = "$CustomerNamePrefix.local"
                        Login = 'sysadmin'
                        Password = $SysAdminPassword
                        ResourceGroup = $ResourceGroupName
                        Connection = "https://$($CustomerNamePrefix.ToLower()).$($Location).cloudapp.azure.com/rdweb"
                    }
                }
                Completed = $false
                Error = $null
                Log = $Log
                CredentialFile = "$CredentialDirectory\SBSDeployment-$($SyncHash.InstanceId).json"
            }

            $null = invoke-operation -synchash $SyncHash -root $SyncHash.Root -Log $SyncHash.Log -code {
                try {
                    #$null = Select-AzureRmProfile -Path $SyncHash.DeploymentJob.CredentialFile
                    $null = Add-AzureRmAccount -Credential $SyncHash.Credential -TenantId $SyncHash.TenantId -SubscriptionId $SyncHash.SubscriptionId
                    $null = New-AzureRmResourceGroupDeployment -TemplateUri $Global:TemplateUrl `
                    -TemplateParameterObject $SyncHash.DeploymentParameters -ResourceGroupName $SyncHash.ResourceGroupName -Force
                    if ($? -eq $false) {
                        throw $Error[1]
                    }
                } catch {
                    $SyncHash.DeploymentJob.Error = $_.Exception
                }
                
            }
            
            $null = Invoke-Operation -synchash $SyncHash -log $SyncHash.Log -root $SyncHash.Root -code {
                try {
                    $null = Add-AzureRmAccount -Credential $SyncHash.Credential -TenantId $SyncHash.TenantId -SubscriptionId $SyncHash.SubscriptionId
                    $DeploymentStatus = Get-AzureRmResourceGroupDeployment -ResourceGroupName $SyncHash.ResourceGroupName
                    
                    while (((($DeploymentStatus.where{$_.ProvisioningState -eq 'Running'}).count -gt 0) -or ((new-timespan -start $SyncHash.DeploymentStart -end (get-date)).TotalMinutes -lt 1)) -and ($SyncHash.DeploymentJob.Error -eq $null)) {
                        $Start = $SyncHash.DeploymentStart
                        $End = Get-Date
                        $Duration = New-TimeSpan -Start $Start -End $End
                        
                        $SyncHash.DeploymentJob.Duration = $("{0:HH:mm:ss}" -f ([datetime]$Duration.Ticks))
                        $SyncHash.DeploymentJob.Status.Deployment = @()
                        foreach ($Item in $DeploymentStatus) {
                            $Status = new-object -TypeName psobject -Property @{
                                Name = $Item.DeploymentName
                                Status = $Item.ProvisioningState
                            }
                            $SyncHash.DeploymentJob.Status.Deployment += $Status
                        }
                        start-sleep -Seconds 10
                        $DeploymentStatus = Get-AzureRmResourceGroupDeployment -ResourceGroupName $SyncHash.ResourceGroupName
                    }


                    foreach ($Item in $DeploymentStatus) {
                        $Status = new-object -TypeName psobject -Property @{
                            Name = $Item.DeploymentName
                            Status = $Item.ProvisioningState
                        }
                        $SyncHash.DeploymentJob.Status.Deployment += $Status
                    }
                    
                } catch {
                    $SyncHash.DeploymentJob.Error = $Error[0].ToString()
                }
                finally {
                    $Duration = New-TimeSpan -Start $Start -End (get-date) 
                    $SyncHash.DeploymentJob.Duration = $("{0:HH:mm:ss}" -f ([datetime]$Duration.Ticks))
                    $SyncHash.DeploymentJob.Completed = $true
                }


            }
            if ($AsJob) {
                # return [ref]$SyncHash.DeploymentJob
            }
            else {
                while ($SyncHash.DeploymentJob.Completed -ne $true) {
                    Write-Progress -id 100 -Activity "Deploying Azure Solution ($($SyncHash.DeploymentJob.Duration))" -PercentComplete -1
                    $i = 0
                    foreach ($Item in $SyncHash.DeploymentJob.Status.Deployment) {
                        Write-Progress -Activity $Item.Name -Status $Item.Status -ParentId 100 -PercentComplete -1 -id $i
                        $i++
                    }
                    start-sleep -Seconds 10
                }
                
            }
            #remove-item -Path $SyncHash.DeploymentJob.CredentialFile -Force -ErrorAction Ignore
            if ($SyncHash.DeploymentJob.Error -ne $null) {
                throw $SyncHash.DeploymentJob.Error
            }
        }
        catch {

            # Remove-AzureRmResourceGroup -Name $ResourceGroupName -Force
            write-log -Type Error "Error while deploying solution: $($SyncHash.DeploymentJob.Error)."
        }
        finally {
            ([ref]$SyncHash.DeploymentJob).value
        }
    }
    
    end {
    } 
}