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"
)

. "$ScriptsFolderPath\dashboard_common.ps1"

$nc_resource_group = "NC-$EnvironmentName"
$arrayList = [System.Collections.ArrayList]@()
$VerbosePreference = "continue"

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

    Write-Host "$((Get-Date).ToString()) - Enabling system-assigned managed identity on $VmName"
    $vm = Get-AzVM -ResourceGroupName $nc_resource_group -Name $VmName
    Update-AzVM -ResourceGroupName $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
        "Sku"                = $Sku
        "VmSize"             = $VmSizeName
        "Offer"              = $Offer
        "Publisher"          = $Publisher
        "Version"            = $Version
        "StorageAccountType" = $StorageAccountType
        "OsType"             = $OS
        "Location"           = $Location
        "Zone"               = $Zone
        "IsEvalKey"          = $isEvalKey
        "CreatePublicIp"     = $CreatePublicIp
        "SetupUrl"           = $SetupUrl
        "ImageVersion"       = $ImageVersion
        "UseNCacheImage"     = $UseNCacheImage.ToBool()
        "ServerProfile"      = $ServerPlan
        "Edition"            = $Edition
        "LicenseDuration"    = $LicenseDuration
        "RuleType"           = $RuleType
    }

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

    if (![string]::IsNullOrWhiteSpace($LicenseKey)) {
        $dict["LicenseKey"] = $LicenseKey
    }
    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 {


    $vms = Get-AzVM -ResourceGroupName $nc_resource_group -Status

    $results = foreach ($vm in $vms) {
        $nicId = $vm.NetworkProfile.NetworkInterfaces[0].Id
        $nicName = ($nicId -split '/')[8]
        $nic = Get-AzNetworkInterface -ResourceGroupName $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 $nc_resource_group -Name $publicIpName
            $publicIp = $publicIpObj.IpAddress

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

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

    $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 ExecuteCommands {
    $zoneList = @($Zone)
    ValidateFieldsFunction
    $allVms = CreateServersList
    # CheckQuota
    # GetAzureRestictionCapacity
    $encodedScript = CreateAndEncodeScript
    Get-AzResourceGroup -Name $nc_resource_group -ErrorVariable notPresent -ErrorAction SilentlyContinue | Out-Null
    if (!$notPresent) { 
        throw "Environment with same name already exists"
    }
    $AzresourceGroup = New-AzResourceGroup -Name $nc_resource_group -Location $Location -ErrorAction Stop
    $tags = AddTags
    Write-Host "Deploying Resources..."
    New-AzResourceGroupDeployment -ResourceGroupName $nc_resource_group -TemplateFile $ArmTemplatePath -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() -DeploymentDebugLogLevel All -Verbose -ErrorVariable MyError | Out-Null
    if ($MyError) {
        throw "Exception $($MyError.Exception.Message)"
    }
    New-AzTag -ResourceId $AzresourceGroup.ResourceId -Tag $tags | Out-Null
}

$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 -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()) - 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()) - Installed NCache on server $vm"
        } -ArgumentList $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()) - 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()) - Installed NCache on server $vm" 
        } -ArgumentList $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 -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 $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"
    }
}



try {
    $stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
    if (-not (Get-AzContext)) {
        Connect-AzAccount
        if ((Get-AzContext)) {
            ExecuteCommands
            InstallNCache
            ShowDeployedEvs
        }
    }
    else {
        ExecuteCommands
        InstallNCache
        ShowDeployedEvs
    }    
    $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 ($($_.Exception.Message) -ne "Environment with same name already exists") {
        Write-Error "Resource group creation failed. Rolling back changes....."
        Remove-AzResourceGroup -Name $nc_resource_group -Force -ErrorAction SilentlyContinue
    }
}