frameworkResources/Scripts/create_environment.ps1

Param(
    [Parameter(Mandatory)]
    $EnvironmentName,
    [Parameter(Mandatory)]
    $Location,
    [string]$NetworkCidr = "33.7.0.0/26",
    [Parameter(Mandatory)]
    $VmCount,
    [Parameter(Mandatory)]
    $Zone,
    [Parameter(Mandatory)]
    $Version,
    [Parameter(Mandatory)]
    $OS,
    [string]$StorageAccountType = "Standard_LRS",
    [Parameter(Mandatory)]
    $VmSizeName,
    [Parameter(Mandatory)]
    $FirstName,
    [Parameter(Mandatory)]
    $LastName,
    [Parameter(Mandatory)]
    $EvaluationKey,
    [Parameter(Mandatory)]
    $Email,
    [string]$CompanyName = "",
    [string]$UserName = "ncadmin",
    [Parameter(Mandatory)]
    $Password,
    $PrivateIps,
    [bool]$isEvalKey,
    [bool]$CreatePublicIp,
    [string]$RuleType,
    [string[]]$WhiteListIps,
    [string]$LicenseDuration,
    [string]$Publisher = "ncache",
    [string]$Offer = "ent-linux-536",
    [string]$Sku = "ent-linux-53",
    [string]$ImageVersion,
    [string]$ArmTemplatePath = ".\CreateResourceGroup.json",
    [Parameter(Mandatory)]
    [string]$SetupUrl,
    [switch]$UseNCacheImage,
    [string]$ServerPlan,
    [string]$LicenseKey,
    [string]$Edition,
    [string]$ScriptsFolderPath = ".\Resources\Scripts",
    [string]$PrivateImageId,
    [string]$InstallNCacheDir = "C:\Program Files\NCache",
    [switch]$EnableDetailMonitoring,
    [string]$VNetName,
    [string]$SubnetName
)



. "$ScriptsFolderPath\NCache\installation.ps1"
. "$ScriptsFolderPath\dashboard_common.ps1"
. "$ScriptsFolderPath\log_monitoring_common.ps1"
. "$ScriptsFolderPath\common.ps1"

$global:nc_resource_group = "NC-$EnvironmentName"
$InstallNCacheDir = Get-NCacheInstallDir -OS $OS -InstallNCacheDir $InstallNCacheDir

$arrayList = [System.Collections.ArrayList]@()
$VerbosePreference = "continue"
$global:isUserResurceGroup = $false

function Enable-SystemAssignedIdentity {
    param(
        [Parameter(Mandatory)][string]$VmName
    )

    Write-Host "$((Get-Date).ToString('yyyy-MM-dd')) - Enabling system-assigned managed identity on VM '$VmName' for secure Azure access"

    $vm = Get-AzVM -ResourceGroupName $global:nc_resource_group -Name $VmName
    Update-AzVM -ResourceGroupName $global:nc_resource_group -VM $vm -IdentityType SystemAssigned
}

function CommaSepratedIps {
    $commaSepratedIps = ""
    foreach ($ip in $PrivateIps) {
        $commaSepratedIps += $ip + ","
    }
    $commaSepratedIps.TrimEnd(',')
    return $commaSepratedIps | Out-Null
}


function CreateStartupCommand {
    param(
        [string]$Key,
        [string]$firstName,
        [string]$lastName,
        [string]$email,
        [string]$company,
        [string]$environment
    )
    $cloudType = "azs"
    $evalFlag = [int]$isEvalKey 
    $StartupScriptUrl = "https://ncachedeployments.s3.us-east-1.amazonaws.com/5.3.6-tools/StartupScriptLinux.sh"
    $paramStr = "-InstallType $cloudType -Key $Key -FirstName $FirstName -LastName $LastName -Email $Email -Environment $EnvironmentName -IsEvalKey $evalFlag "

    if (-not $isEvalKey) {
        $paramStr += "-LicenseDuration $LicenseDuration "
    }
    if (![string]::IsNullOrWhiteSpace($company)) {
        $paramStr += "-Company $company "
    }

    
    if ($OS -eq "Windows") {
        $ips = CommaSepratedIps
        $paramStr += "-PrivateIps $ips -Username $UserName -Password $Password "
        return ("Invoke-WebRequest -Uri https://ncachedeployments.s3.us-east-1.amazonaws.com/5.3.6-tools/StartupScriptWin_v6.ps1 -OutFile C:/StartupScriptWin_v6.ps1;Unblock-File -Path C:/StartupScriptWin_v6.ps1;
        C:/StartupScriptWin_v6.ps1 $paramStr"
)
    }
    $paramStr += " -PrivateIps $PrivateIps "
    return ("#!/bin/sh
sleep 30; wget -O /tmp/StartupScriptLinux.sh $StartupScriptUrl; chmod +x /tmp/StartupScriptLinux.sh; /tmp/StartupScriptLinux.sh $paramStr"
 -replace "`r`n", "`n")
}
 

function EncryptStartupCommand {
    param(
        [string]$StartupCommand
    )
    $bytes = [System.Text.Encoding]::UTF8.GetBytes($StartupCommand)
    $encoded = [Convert]::ToBase64String($bytes)
    return $encoded
}

function Test-CIDR {
    param ([string]$CIDR)
    $cidrPattern = '^((25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})\.){3}(25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})\/(3[0-2]|[1-2]?[0-9])$'
    return $CIDR -match $cidrPattern
}


function ValidateCIDRBlocks {
    param (
        [string]$CidrBlocks
    )

    $result = @{
        valid      = $false
        errors     = @()
        validCIDRs = @()
    }

    if (-not $CidrBlocks.Trim()) {
        $result.errors += "CIDR block cannot be empty"
        return $result
    }

    $multipleSpaces = "/\\s{2,}/"
    $missingComma = "/[^,\\s]+\\s+[^,\\s]+/"


    $cidrList = $CidrBlocks -split "," |
    ForEach-Object { $_.Trim() } |
    Where-Object { $_.Length -gt 0 }

    if ($cidrList.Count -eq 0) {
        $result.errors += "No valid CIDR blocks provided"
        return $result
    }

    $seen = @{}
    foreach ($cidr in $cidrList) {
        if (-not (Test-CIDR $cidr)) {
            $result.errors += "Invalid CIDR format: $cidr"
        }
        elseif ($seen.ContainsKey($cidr)) {
            $result.errors += "Duplicate CIDR blocks are not allowed"
        }
        else {
            $seen[$cidr] = $true
        }
    }

    $result.valid = ($result.errors.Count -eq 0)
    $result.validCIDRs = $seen.Keys

    return $result
}



function ValidateFields() {
    param(
        [string]$environmentName,
        [string]$location,
        [string]$firstName,
        [string]$lastName,
        [string]$licenseKey,
        [string]$email,
        [string]$password,
        [string]$companyName,
        [string]$vmSizeName
    )

    if ([string]::IsNullOrWhiteSpace($EnvironmentName)) {
        throw "Environment Name cannot be empty"
    }

    if ([string]::IsNullOrWhiteSpace($Location)) {
        throw "Location cannot be empty"
    }

    if ($VmCount -lt 1) {
        throw "Vm Count Name cannot be less than 0"
    }

    if ([string]::IsNullOrWhiteSpace($FirstName)) {
        throw "First name is required"
    }

    if ([string]::IsNullOrWhiteSpace($LastName)) {
        throw "Last Name is required"
    }

    if ([string]::IsNullOrWhiteSpace($Email)) {
        throw "Email is required"
    }

    if ([string]::IsNullOrWhiteSpace($Password)) {
        throw "Password is required"
    }

    if ([string]::IsNullOrWhiteSpace($VmSizeName)) {
        throw "VmSize Is Required is required"
    }

    if ([string]::IsNullOrEmpty($vmSizeName)) {
        throw "InCorrect Vm Size Provided"
    }
}



function AddTags {
    $dict = @{
        "EvaluationKey"            = $EvaluationKey
        "FirstName"                = $FirstName
        "LastName"                 = $LastName
        "Email"                    = $Email
        "UserName"                 = $UserName
        "EnvironmentName"          = $EnvironmentName
        "VmSize"                   = $VmSizeName
        "Version"                  = $Version
        "StorageAccountType"       = $StorageAccountType
        "OsType"                   = $OS
        "Location"                 = $Location
        "Zone"                     = $Zone
        "IsEvalKey"                = $isEvalKey
        "CreatePublicIp"           = $CreatePublicIp
        "SetupUrl"                 = $SetupUrl
        "UseNCacheImage"           = $UseNCacheImage.ToBool()
        "ServerProfile"            = $ServerPlan
        "Edition"                  = $Edition
        "LicenseDuration"          = $LicenseDuration
        "RuleType"                 = $RuleType
        "EnableDetailedMonitoring" = $EnableDetailMonitoring.ToBool()
        "CreateVnet"               = $true
        "Vnet"                     = $VNetName
        "Subnet"                   = $SubnetName
        "InstallNCacheDir"         = $InstallNCacheDir
        "Password"                 = $Password
    }

    if (![string]::IsNullOrWhiteSpace($LicenseDuration)) {
        $dict["LicenseDuration"] = $LicenseDuration
    }
    
    if (![string]::IsNullOrWhiteSpace($CompanyName)) {
        $dict["Company"] = $CompanyName
    }

    if (![string]::IsNullOrWhiteSpace($LicenseKey)) {
        $dict["LicenseKey"] = $LicenseKey
    }

    if (![string]::IsNullOrWhiteSpace($Offer)) {
        $dict["Offer"] = $Offer
    }

    if (![string]::IsNullOrWhiteSpace($Publisher)) {
        $dict["Publisher"] = $Publisher
    }

    if (![string]::IsNullOrWhiteSpace($ImageVersion)) {
        $dict["ImageVersion"] = $ImageVersion
    }

    if (![string]::IsNullOrWhiteSpace($Sku)) {
        $dict["Sku"] = $Sku
    }

    if (![string]::IsNullOrWhiteSpace($PrivateImageId)) {
        $dict["ImageId"] = $PrivateImageId
    }

    if (![string]::IsNullOrEmpty($VNetName)) {
        $dict["CreateVnet"] = $false
    }

    return $dict
}

function CheckQuota {
    (Get-AzComputeResourceSku -Location $Location |
    Where-Object { $_.ResourceType -eq "virtualMachines" -and $_.Name -eq $VmSizeName }).Capabilities |
    Where-Object { $_.Name -eq "vCPUs" } |
    Select-Object -ExpandProperty Value

    $sku = Get-AzComputeResourceSku -Location $Location `
    | Where-Object { $_.ResourceType -eq "virtualMachines" -and $_.Name -eq $VmSizeName }

    $family = $sku.Family
    $vcpus = ($sku.Capabilities | Where-Object { $_.Name -eq "vCPUs" }).Value

    $info = [PSCustomObject]@{
        Name   = $sku.Name
        Family = $family
        vCPUs  = $vcpus
    }
    $Usage = Get-AzVMUsage -Location $Location | Where-Object { $_.Name.Value -eq $family }

    ValidateQuota -currentQuota $($Usage.CurrentValue) -limitQuota $($Usage.Limit) -vCpus $vcpus
}

function ValidateQuota() {
    param(
        [int]$currentQuota,
        [int]$limitQuota,
        [int]$vCpus
    )
    $myQuota = $VmCount * $vCpus
    if ($myQuota -gt $limitQuota) {
        throw "Quota limit exceeded"
    }
}

function ValidateFieldsFunction {
    ValidateFields -environmentName $EnvironmentName -location $Location -firstName $FirstName -lastName $LastName -licenseKey $LicenseKey  -email $Email -password $Password -companyName $CompanyName -vmsizeName $VmSizeName
    $result = ValidateCIDRBlocks -CidrBlocks $NetworkCidr

    if (-not [string]::IsNullOrEmpty($result.errors) ) {
        throw $result.errors
    }
}

function CreateServersList {
    for ($i = 1; $i -le $VmCount; $i++) {
        $arrayList.Add("NC-Server-$i") | Out-Null
    }
    return $arrayList
}

function CreateAndEncodeScript {
    $scriptCode = CreateStartupCommand -key $LicenseKey -firstName $FirstName -lastname $LastName -email $Email -company $CompanyName -environment $EnvironmentName
    $encodedScript = EncryptStartupCommand -StartupCommand $scriptCode
    return $encodedScript

}

function ShowDeployedEvs {

    Write-Host $global:nc_resource_group
    $vms = Get-AzVM -ResourceGroupName $global:nc_resource_group -Status | Where-Object { $_.Tags.ContainsKey("InstallMode") -and $_.Tags["InstallMode"] -eq "server" }

    $results = foreach ($vm in $vms) {
        $nicId = $vm.NetworkProfile.NetworkInterfaces[0].Id
        $nicName = ($nicId -split '/')[8]
        $nic = Get-AzNetworkInterface -ResourceGroupName $global:nc_resource_group -Name $nicName

        $privateIp = $nic.IpConfigurations[0].PrivateIpAddress
        $publicIp = $null
        $managementLink = $null

        if ($nic.IpConfigurations[0].PublicIpAddress) {
            $publicIpId = $nic.IpConfigurations[0].PublicIpAddress.Id
            $publicIpName = ($publicIpId -split '/')[8]
            $publicIpObj = Get-AzPublicIpAddress -ResourceGroupName $global:nc_resource_group -Name $publicIpName
            $publicIp = $publicIpObj.IpAddress

            if ($publicIp) {
                $managementLink = "http://$publicIp`:8251"
            }
        }

        [PSCustomObject]@{
            VMName         = $vm.Name
            PrivateIP      = $privateIp
            PublicIP       = $publicIp
            ManagementLink = $managementLink
            Password       = $Password
            UserName       = $UserName
        }
    }

    $results | Format-Table -AutoSize
}

function GetRestrictedOnes {

    # Pull only SKUs for the given region
    $skus = Get-AzComputeResourceSku -Location $Location |
    Where-Object { $_.ResourceType -eq "virtualMachines" -and $_.Restrictions }

    foreach ($sku in $skus) {
        Write-Output "VM Size: $($sku.Name)"
        Write-Output "Location: $($sku.Locations)"
    
        foreach ($restriction in $sku.Restrictions) {
            Write-Output " Restriction Type : $($restriction.Type)"
            Write-Output " Reason : $($restriction.ReasonCode)"
            if ($restriction.RestrictionInfo.Zones) {
                Write-Output " Restricted Zones : $($restriction.RestrictionInfo.Zones -join ', ')"
            }
        }
        Write-Output "-----------------------------"
    }

}


function GetAzureRestictionCapacity {
    $skus = Get-AzComputeResourceSku -Location $Location |
    Where-Object { $_.ResourceType -eq "virtualMachines" -and $_.Name -eq $VmSizeName }
    foreach ($sku in $skus) {
        if ($sku.Restrictions) {
            $restriction = $sku.Restrictions
            $restrictedZones = $restriction.RestrictionInfo.Zones
            if ($($restriction.ReasonCode) -eq "NotAvailableForSubscription" -and $restrictedZones -contains $Zone) {
                throw "VmSize $VmSizeName is $($restriction.ReasonCode) in Zones $($restriction.RestrictionInfo.Zones -join ', ')"
            }
        }
    }
}


function BlastFromThePast([switch]$EndOfLine) {
    $EscChar = "`r"
    if ($EndOfLine) { $EscChar = "`b" }
    if (!$tickcounter) { Set-Variable -Name "tickcounter" -Scope global -Value 0 -Force -Option AllScope }
    if (!$tickoption) { Set-Variable -Name "tickoption" -Scope global -Value 0 -Force -Option AllScope }
    $chance = Get-Random -Minimum 1 -Maximum 10
    if ($chance -eq 5) { if ($tickoption -eq 1) { $tickoption = 0 }else { $tickoption = 1 } }
    switch ($tickoption) {
        0 {
            switch ($tickcounter) {
                0 { Write-Host "$EscChar|" -NoNewline }
                1 { Write-Host "$EscChar/" -NoNewline }
                2 { Write-Host "$EscChar-" -NoNewline }
                3 { Write-Host "$EscChar\" -NoNewline }
            }
            break;
        }
        1 {
            switch ($tickcounter) {
                0 { Write-Host "$EscChar|" -NoNewline }
                1 { Write-Host "$EscChar\" -NoNewline }
                2 { Write-Host "$EscChar-" -NoNewline }
                3 { Write-Host "$EscChar/" -NoNewline }
            }
            break;
        }
    }
    if ($tickcounter -eq 3) { $tickcounter = 0 }
    else { $tickcounter++ }
}

function CheckIfUserResourceGroupAlreadyExists {
    $resource = Get-AzResourceGroup -Name $EnvironmentName -ErrorAction SilentlyContinue
    return $resource
}

function CheckIfResourceGroupCreatedByUsExists {
    $resource = Get-AzResourceGroup | Where-Object { $_.Tags -and $_.Tags.Contains("EnvironmentName") -and $_.Tags["EnvironmentName"] -eq $EnvironmentName } -ErrorAction Stop 
    if ($resource) {
        throw "Environment with same name already exists"
    }
}
function GetUploadedVms {
    $vms = @(Get-AzVM -ErrorAction Stop -ResourceGroupName $global:nc_resource_group | Where-Object { $_.Tags.ContainsKey("ServerType") -and $_.Tags["ServerType"] -eq "NCache" } | ForEach-Object {
            [PSCustomObject]@{
                Name               = $_.Name
                Location           = $_.Location
                VmSize             = $_.HardwareProfile.VmSize
                OsType             = $_.StorageProfile.OsDisk.OsType
                Zone               = ($_.Zones[0])
                Publisher          = $_.StorageProfile.ImageReference.Publisher
                Offer              = $_.StorageProfile.ImageReference.Offer
                Sku                = $_.StorageProfile.ImageReference.Sku
                Version            = $_.StorageProfile.ImageReference.Version
                NIC                = $_.NetworkProfile.NetworkInterfaces.id
                StorageAccountType = $_.StorageProfile.OsDisk.ManagedDisk.StorageAccountType
                Disk               = $_.StorageProfile.OsDisk.Name
                Tags               = $_.Tags
            }
        }  )

    return , $vms
}

function CreatePublicIp {
    param(
        [string]$MachineName
    )
    return "NC-PUBLIC_IP-$($EnvironmentName)-$($MachineName)"
}
function ExtractAndCreateNIC() {
    param(
        [string]$MachineName
    )
    return "NC-NIC-$($EnvironmentName)-$($MachineName)"
}


function RemoveResources {
    param(
        [string]$VmName,
        [string]$DiskName,
        [string]$NetworkInterfaceName,
        [string]$PublicIpName,
        [string]$ResourceGroup
    )

    if (-not [string]::IsNullOrWhiteSpace($VmName)) {
        Remove-AzVM -ResourceGroupName $ResourceGroup -Name $VmName -Force -ErrorAction SilentlyContinue
    }
    if (-not [string]::IsNullOrWhiteSpace($DiskName)) {
        Remove-AzDisk -ResourceGroupName $ResourceGroup -DiskName $DiskName -Force -ErrorAction SilentlyContinue
    }

    if (-not [string]::IsNullOrWhiteSpace($NetworkInterfaceName)) {
        Remove-AzNetworkInterface -Name $NetworkInterfaceName -ResourceGroupName $ResourceGroup -Force -ErrorAction SilentlyContinue
    } 
    if (-not [string]::IsNullOrWhiteSpace($PublicIpName)) {
        Remove-AzPublicIpAddress -Name $PublicIpName -ResourceGroupName $ResourceGroup -Force -ErrorAction SilentlyContinue
    } 
}

function PerformRollbackOfOurResources {
    if ($global:nc_resource_group) {
        $disks = Get-AzDisk -ResourceGroupName $global:nc_resource_group | Select-Object -ExpandProperty Name
        for ($i = 0 ; $i -lt $arrayList.Count; $i++) {
            $matchingDisks = $disks | Where-Object { $_ -like "*$($arrayList[$i])*" }
            if (-not $matchingDisks) { $matchingDisks = $null }
            $publicIpName = CreatePublicIp -MachineName $arrayList[$i]
            $nicName = ExtractAndCreateNIC -MachineName $arrayList[$i] 
            RemoveResources -VmName $arrayList[$i] -DiskName $matchingDisks -NetworkInterfaceName $nicName -PublicIpName $publicIpName -ResourceGroup $global:nc_resource_group -UseForce $true 
        }
    }
}

function CheckVnetAndSubnetExists {
    if ([string]::IsNullOrWhiteSpace($VNetName) -or [string]::IsNullOrWhiteSpace($SubnetName)) {
        throw "Please provide vnet and subnet when trying to create environment in already existing resource group"
    }
}

function GeneratedPassword {
    Write-Host "Password generated for your vms is $Password"
}


function ExecuteCommands {
    $rg = CheckIfResourceGroupCreatedByUsExists
    $zoneList = @($Zone)
    $allVms = CreateServersList
    # CheckQuota
    # GetAzureRestictionCapacity
    $encodedScript = CreateAndEncodeScript
    $resourceExists = CheckIfUserResourceGroupAlreadyExists
    $AzresourceGroup = $null
    $createVNET = $false
    $activateNCache = ActivatedNCache -UserProvidedKey $LicenseKey -resource $null
    TimeStatus -OS $OS
    if (-not $resourceExists) {
        $AzresourceGroup = New-AzResourceGroup -Name $global:nc_resource_group -Location $Location -ErrorAction Stop
        $createVNET = $true
    }
    else {
        CheckVnetAndSubnetExists
        $global:isUserResurceGroup = $true
        $AzresourceGroup = $resourceExists
        $global:nc_resource_group = $EnvironmentName
    }
    
    $tags = AddTags
    Write-Host "Deploying Resources..."
    New-AzResourceGroupDeployment -ResourceGroupName $nc_resource_group -TemplateFile $ArmTemplatePath -InstallNCacheDir $InstallNCacheDir -location $Location -resource_group_name $EnvironmentName -address_prefixes $NetworkCidr -vm_names $allVms -zone_name $zoneList -publisher_name $Publisher -offer_name $Offer -sku_name $Sku -version_name $Version -os_name $OS -storage_account_type $StorageAccountType -vm_size $VmSizeName -custom_data $encodedScript -userName $UserName -password $Password  -privateIps $PrivateIps -create_public_ip $CreatePublicIp -rule_type $RuleType -white_list_ips $WhiteListIps -image_version $ImageVersion -use_ncache_image $UseNCacheImage.ToBool() -use_private_image $(UsePrivateImage) -private_image_id $PrivateImageId  -vnet_name $VNetName -subnet_name $SubnetName -create_vnet $createVNET -ncache_activated $activateNCache -DeploymentDebugLogLevel All -Verbose -ErrorVariable MyError | Out-Null
    if ($MyError) {
        throw "Exception $($MyError.Exception.Message)"
    }
    New-AzTag -ResourceId $AzresourceGroup.ResourceId -Tag $tags | Out-Null
    EnableMonitoringOnVms
}

$script:ScriptWindows = $null
$script:resourceGroupName = $null
$script:scriptLinux = $null

function EnableCacheServerPublicIp {
    if ($RuleType -eq "Data" -or $RuleType -eq "All") {
        return $true
    }
    return $false
}
function InvokeCommandOnServerWindows {

 $LogFile = "C:\Temp\InstallLog.txt"
 $enableCacheServerIp = EnableCacheServerPublicIp

 $commaSepratedIps = ($PrivateIps -join ",")
    $script = @"
New-Item -ItemType Directory -Force -Path "C:\Temp" | Out-Null
wget "https://ncachedeployments.s3.us-east-1.amazonaws.com/5.3.6-tools/InstallScript_Win_Server.ps1" -OutFile "C:\Temp\InstallScript_Win_Server.ps1"
C:\Temp\InstallScript_Win_Server.ps1 -InstallNCacheDir '$InstallNCacheDir' -Key $EvaluationKey -UserName $UserName -Company '$CompanyName' -FirstName '$FirstName' -LastName '$LastName' -Password $Password -Email $Email -PrivateIps $commaSepratedIps -SetupUrl $SetupUrl -LicenseKey '$LicenseKey' -EnableCacheServerPublicIp `$$enableCacheServerIp -Environment '$EnvironmentName' -LicenseDuration '$LicenseDuration'
"@


    foreach ($server in $arrayList) {
        Write-Host "$((Get-Date).ToString('yyyy-MM-dd')) - Downloading and Installing NCache on $server"
    }

    $jobs = foreach ($server in $arrayList) {
        Start-Job -ScriptBlock {
            param($rg, $vm, $script)
            Invoke-AzVMRunCommand -ResourceGroupName $rg -VMName $vm -CommandId 'RunPowerShellScript' -ScriptString $script
            Write-Host "$((Get-Date).ToString('yyyy-MM-dd')) - Installed NCache on server $vm"
        } -ArgumentList $global:nc_resource_group, $server, $script
    }

    $dots = ""
    while (@($jobs | Where-Object { $_.State -eq "Running" }).Count -gt 0) {
        $dots += "."
        Write-Host -NoNewline "`r$dots"
        Start-Sleep -Seconds 5
        $jobs = $jobs | Get-Job
    }

    # Clear dots line
    $clearLine = " " * $dots.Length
    Write-Host -NoNewline "`r$clearLine`r"

    # while (@($jobs | Where-Object { $_.State -eq "Running" }).Count -gt 0) {
    # BlastFromThePast;
    # Start-Sleep -Milliseconds 100
    # }
    # BlastFromThePast -Finished;


    $jobs | Wait-Job
    $jobs | Receive-Job

}


function InvokeCommandOnServerLinux {

    foreach ($server in $arrayList) {
        Write-Host "$((Get-Date).ToString('yyyy-MM-dd')) - Downloading and Installing NCache on $server"
    }
 $enableCacheServerIp = EnableCacheServerPublicIp

    $command = CreateScriptBlockLinux
    $jobs = foreach ($server in $arrayList) {
        Start-Job -ScriptBlock {
            param($rg, $vm, $script)
            Invoke-AzVMRunCommand -ResourceGroupName $rg -VMName $vm -CommandId 'RunShellScript' -ScriptString $script
            Write-Host "$((Get-Date).ToString('yyyy-MM-dd')) - Installed NCache on server $vm" 
        } -ArgumentList $global:nc_resource_group, $server, $command
    }

    $jobs | Wait-Job
    $jobs | Receive-Job
}


function CreateScriptBlockLinux {
    return "sudo wget -O /tmp/InstallScript_Lin_Server.sh https://ncachedeployments.s3.us-east-1.amazonaws.com/5.3.6-tools/InstallScript_Lin_Server.sh;
    chmod +x /tmp/InstallScript_Lin_Server.sh;
    /tmp/InstallScript_Lin_Server.sh -InstallNCacheDir '$InstallNCacheDir' -FirstName '$FirstName' -LastName '$LastName' -Email $Email -Key $EvaluationKey -Company '$CompanyName' -PrivateIps $PrivateIps -SetupUrl '$SetupUrl' -LicenseKey '$LicenseKey' -Environment '$EnvironmentName' -EnableCacheServerPublicIp $enableCacheServerIp -LicenseDuration '$LicenseDuration'"

}


function InstallClient {
    If ($OS -eq "Windows") {
        InvokeCommandOnServerWindows 
    }
    else {
        InvokeCommandOnServerLinux
    }
}

function InstallNCache {
    foreach ($server in $arrayList) {
        Enable-SystemAssignedIdentity -VmName $server
    }

    if (-not $UseNCacheImage) {
        InstallClient
    }

    Update-NcAzDashboards -ScriptsFolderPath $ScriptsFolderPath -ResourceGroupName $global:nc_resource_group -CacheName "demoCache" -SkipClient
}

function CheckIfSizeIsAvailable {

    $restricted = Get-AzComputeResourceSku -Location $Location | Where-Object { $_.Name -eq $VmSizeName } | Select-Object Name, Restrictions

    if ($restricted.Restrictions[0].ReasonCode -eq "NotAvailableForSubscription") {
        throw "Specified vmSize is restricted in $location for your subscription"
    }
}

function UsePrivateImage {
    if ([string]::IsNullOrWhiteSpace($PrivateImageId)) {
        return $false
    }
    return $true
}

function EnableMonitoringOnVms {
    $enableMonitoring = $EnableDetailMonitoring.ToBool()
    if ($enableMonitoring) {
        Install-NcAzMonitoringResources -ResourceGroupName $nc_resource_group
        foreach ($server in $arrayList) {
            Install-NcAzMonitoringOnVM -ResourceGroupName $nc_resource_group -VmName $server
        }
    }
}

try {
    $stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
    if (-not (Get-AzContext)) {
        NoContext
        Connect-AzAccount
        if ((Get-AzContext)) {
            ExecuteCommands
            InstallNCache
            ShowDeployedEvs
            ResourceGroupLink -RgName $global:nc_resource_group
        }
    }
    else {
        ExecuteCommands
        InstallNCache
        ShowDeployedEvs
        ResourceGroupLink -RgName $global:nc_resource_group
    }    
    $stopwatch.Stop()
    Write-Host "Execution Time: $($stopwatch.Elapsed.ToString())"

}
catch {
    if ($MyError) {
        Write-Host "An error occurred: $($MyError.Exception.Message)"
    }
    Write-Error "Error: $($_.Exception.Message)"
    if ($global:isUserResurceGroup) {
        Write-Error "Resource group creation failed. Rolling back changes....."
        PerformRollbackOfOurResources
    }
    else {
        if ($($_.Exception.Message) -ne "Environment with same name already exists") {
            Write-Error "Resource group creation failed. Rolling back changes....."
            Remove-AzResourceGroup -Name $global:nc_resource_group -Force -ErrorAction SilentlyContinue
        }
    }
    throw $($_.Exception.Message)
}