frameworkResources/Scripts/new_cache.ps1

param(
    [Parameter(Mandatory = $true)]
    [string]$EnvironmentName,

    [Parameter(Mandatory = $true)]
    [string]$Name,
    
    [Parameter(Mandatory = $true)]
    [ValidateSet("Replicated", "Partitioned", "PartitionReplica")]
    [string]$Topology,
    
    [Parameter(Mandatory = $true)]
    [long]$Size,

    [Parameter(Mandatory = $false)]
    [int]$CleanupInterval,

    [Parameter(Mandatory = $false)]
    [int]$ClusterPort,

    [Parameter(Mandatory = $false)]
    [string]$DefaultPriority,

    [Parameter(Mandatory = $false)]
    [string]$EvictionPolicy,

    [Parameter(Mandatory = $false)]
    [decimal]$EvictionRatio,

    [Parameter(Mandatory = $false)]
    [string]$SerializationFormat,

    [Parameter(Mandatory = $false)]
    [switch]$NoLogo,

    [Parameter(Mandatory = $false)]
    [string]$Path,

    [Parameter(Mandatory = $false)]
    [int]$Port,

    [Parameter(Mandatory = $false)]
    [string]$ReplicationStrategy,

    [Parameter(Mandatory = $false)]
    [PSCredential]$Credentials,

    [Parameter(Mandatory = $false)]
    [string]$InMemoryStoreType,

    [Parameter(Mandatory = $false)]
    [switch]$UseExistingIndex,

    [Parameter(Mandatory = $false)]
    [string]$PersistenceStoreName,

    [Parameter(Mandatory = $false)]
    [switch]$NewPersistenceStore,

    [Parameter(Mandatory = $false)]
    [string]$PersistenceStoreConnectionString,

    [string]$ScriptsFolderPath = ".\Resources\Scripts"
)

. "$ScriptsFolderPath\dashboard_common.ps1"

function Get-EnvName {
    param (
        [Parameter(Mandatory = $true)]
        [string]$EnvironmentName
    )
    # Get all RGs with EnvironmentName tag
    $all_resource_groups = Get-AzResourceGroup -ErrorAction Stop | Where-Object { $_.Tags -and $_.Tags.ContainsKey("EnvironmentName") } 

    if (-not $all_resource_groups) {
        throw "No resource groups found with tag 'EnvironmentName'."
    }

    # Match the exact EnvironmentName
    $matched_group = $all_resource_groups | Where-Object {
        $_.Tags["EnvironmentName"] -eq $EnvironmentName
    }

    if (-not $matched_group) {
        throw "No resource group found with EnvironmentName = '$EnvironmentName'."
    }

    return $matched_group.ResourceGroupName
}

function GetVms {
    Param(
        [Parameter(Mandatory = $true)]
        [string]$EnvironmentName
    )

    #Filtering VMs using ResourceGroups and Cache Tags
    $vms = Get-AzVM -Status -ResourceGroupName $EnvironmentName -ErrorAction Stop |
    Where-Object { $_.Tags.ContainsKey("Caches") } 

    return $vms
}


function Get-VMPrivateIps {
    param(
        [Parameter(Mandatory = $true)]
        [array]$VMs,

        [Parameter(Mandatory = $true)]
        [string]$ResourceGroupName
    )


    $VMPrivateIps = foreach ($vm in $VMs) {
        foreach ($nicRef in $vm.NetworkProfile.NetworkInterfaces) {
            $nicName = ($nicRef.Id -split "/")[-1]
            $nic = Get-AzNetworkInterface -Name $nicName -ResourceGroupName $ResourceGroupName -ErrorAction Stop

            [PSCustomObject]@{
                VMName     = $vm.Name
                NicName    = $nic.Name
                PrivateIps = ($nic.IpConfigurations | Select-Object -ExpandProperty PrivateIpAddress)
                OsType     = $vm.StorageProfile.OsDisk.OsType
            }
        }
    }


    return , $VMPrivateIps
}
function Get-LinuxCommand {
    param(
        [Parameter(Mandatory = $true)]
        [object]$machineInfo,
        
        [Parameter(Mandatory = $true)]
        [string]$cacheName
    )

    $ipList = ($machineInfo.PrivateIps -join ",")

    $command = "/opt/ncache/bin/tools/new-cache $($cacheName) -server `"$ipList`" -topology $($Topology) -size $($Size)"

    if ($ClusterPort) { $command += " -clusterport $ClusterPort" }
    if ($DefaultPriority) { $command += " -defaultpriority $DefaultPriority" }
    if ($EvictionPolicy) { $command += " -evictionpolicy $EvictionPolicy" }
    if ($EvictionRatio) { $command += " -evictionratio $EvictionRatio" }
    if ($Inproc) { $command += " -inproc" }
    if ($CleanupInterval) { $command += " -cleanupinterval $CleanupInterval" }
    if ($Credentials) { $command += " -userid $($Credentials.UserName)" }
    if ($SerializationFormat) { $command += " -serializationformat $SerializationFormat" }
    if ($ReplicationStrategy) { $command += " -replicationstrategy $ReplicationStrategy" }
    if ($InMemoryStoreType) { $command += " -inmemorystoretype $InMemoryStoreType" }
    if ($UseExistingIndex) { $command += " -useexistingindex" }
    if ($PersistenceStoreName) { $command += " -persistencestorename $PersistenceStoreName" }
    if ($NewPersistenceStore) { $command += " -newpersistencestore" }
    if ($PersistenceStoreConnectionString) { $command += " -persistencestoreconnectionstring `"$PersistenceStoreConnectionString`"" }
    if ($Port) { $command += " -port $Port" }
    if ($NoLogo) { $command += " -nologo" }
    if ($Path) {
        if (Test-Path $Path) {

            #vmPath
            $vmPath = "./config-temp.ncconf"
            # Load file content into a variable
            $configContent = Get-Content -Path $Path -Raw

            # (Optional) Save it to ./config.nconf if needed
            $command = "echo `'$configContent`' > $vmPath ;" + $command

            # Prepend the -Path flag to the command
            $command += " -path $vmPath"


            $command += "; rm -f $vmPath"
        }
        else {
            throw "The specified path '$Path' does not exist."
        }
    }
    return $command
}

function Get-WindowsCommand {
    param(
        [Parameter(Mandatory = $true)]
        [object]$machineInfo,
        
        [Parameter(Mandatory = $true)]
        [string]$cacheName
    )

    $ipList = ($machineInfo.PrivateIps -join ",")

    $command = "New-Cache -Name $($cacheName) -Server `"$ipList`" -Topology $($Topology) -Size $($Size)"

    if ($ClusterPort) { $command += " -ClusterPort $ClusterPort" }
    if ($DefaultPriority) { $command += " -DefaultPriority $DefaultPriority" }
    if ($EvictionPolicy) { $command += " -EvictionPolicy $EvictionPolicy" }
    if ($EvictionRatio) { $command += " -EvictionRatio $EvictionRatio" }
    if ($Inproc) { $command += " -InProc" }
    if ($CleanupInterval) { $command += " -CleanupInterval $CleanupInterval" }
    if ($Credentials) { 
        $command += " -UserName $($Credentials.UserName)"
        # Password usually handled as SecureString
    }
    if ($SerializationFormat) { $command += " -SerializationFormat $SerializationFormat" }
    if ($ReplicationStrategy) { $command += " -ReplicationStrategy $ReplicationStrategy" }
    if ($InMemoryStoreType) { $command += " -InMemoryStoreType $InMemoryStoreType" }
    if ($UseExistingIndex) { $command += " -UseExistingIndex" }
    if ($LuceneIndexPath) { $command += " -LuceneIndexPath `"$LuceneIndexPath`"" }
    if ($PersistenceStoreName) { $command += " -PersistenceStoreName $PersistenceStoreName" }
    if ($NewPersistenceStore) { $command += " -NewPersistenceStore" }
    if ($PersistenceStoreConnectionString) { $command += " -PersistenceStoreConnectionString `"$PersistenceStoreConnectionString`"" }
    if ($Port) { $command += " -Port $Port" }
    if ($NoLogo) { $command += " -NoLogo" }
    if ($Path) {
        if (Test-Path $Path) {

            # vmPath
            $vmPath = ".\config-temp.ncconf"

            # Load file content into a variable
            $configContent = Get-Content -Path $Path -Raw

            # Save it to config-temp.nconf
            $command = "Set-Content -Path $vmPath -Value `'$configContent`'; " + $command 

            # Prepend the -Path flag to the command
            $command = "$command -Path `"$vmPath`""

            # Clean up the temp file after execution
            $command += "; Remove-Item -Force $vmPath"
        }
        else {
            throw "The specified path '$Path' does not exist."
        }
    }
    return $command
}
function Get-CommandForCache {
    param(
        [Parameter(Mandatory = $true)]
        [object]$machineInfo,
        
        [Parameter(Mandatory = $true)]
        [string]$cacheName
    )

    if ($machineInfo.OsType -eq "Windows") {
        return @{
            CommandId = "RunPowerShellScript"
            VMName    = $machineInfo.FirstVMName
            Script    = Get-WindowsCommand -machineInfo $machineInfo -cacheName $cacheName
        }
    }
    elseif ($machineInfo.OsType -eq "Linux") {
        return @{
            CommandId = "RunShellScript"
            VMName    = $machineInfo.FirstVMName
            Script    = Get-LinuxCommand -machineInfo $machineInfo -cacheName $cacheName
        }
    }
    else {
        throw "Unknown OS type for VM: $($Vm.Name)"
    }
}

function Get-MachineInfo {
    param(
        [Parameter(Mandatory = $true)]
        [array]$VMInfo
    )

    if (-not $VMInfo -or $VMInfo.Count -eq 0) {
        throw "VMInfo array is empty."
    }

    $firstVM = $VMInfo[0]

    # Aggregate all IPs from all VMs into a single array
    $allIPs = $VMInfo | ForEach-Object { $_.PrivateIps } | ForEach-Object { $_ }

    [PSCustomObject]@{
        FirstVMName = $firstVM.VMName
        PrivateIps  = $allIPs
        OsType      = $firstVM.OsType
    }
}
function Add-CacheTagToVMs {
    param(
        [Parameter(Mandatory = $true)]
        [string]$ResourceGroupName,

        [Parameter(Mandatory = $true)]
        [string]$CacheName,

        [Parameter(Mandatory = $true)]
        [array]$VMs   # array of VM objects with .Name
    )

    foreach ($vm in $VMs) {
        try {
            Write-Output "Updating tags for VM: $($vm.Name)"

            # Get existing VM
            $vmResource = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $vm.VMName

            # Convert tags dictionary to hashtable
            $tags = @{}
            if ($vmResource.Tags) {
                foreach ($k in $vmResource.Tags.Keys) {
                    $tags[$k] = $vmResource.Tags[$k]
                }
            }

            # Update/add "Caches" tag
            if ($tags.ContainsKey("Caches")) {
                if ($tags["Caches"] -notmatch "\b$CacheName\b") {
                    $tags["Caches"] = "$($tags["Caches"]),$CacheName"
                }
            }
            else {
                $tags["Caches"] = $CacheName
            }

            # Apply updated tags
            Set-AzResource -ResourceId $vmResource.Id -Tag $tags -Force -ErrorAction Stop | Out-Null 
            Write-Output "Cache '$CacheName' added to VM '$($vm.VMName)'"
        }
        catch {
            Write-Output "Failed to update tags on $($vm.VMName): $_"
        }
    }

    Update-NcAzDashboards -ScriptsFolderPath $ScriptsFolderPath -ResourceGroupName $ResourceGroupName -CacheName $CacheName -SkipClient
}


function Invoke-CacheCommand {
    param(
        [Parameter(Mandatory = $true)]
        [string]$ResourceGroupName,

        [Parameter(Mandatory = $true)]
        [object]$Command
    )

    Write-Output "Executing on VM: $($Command.VMName) => $($Command.Script)"

    $result = Invoke-AzVMRunCommand `
        -ResourceGroupName $ResourceGroupName `
        -VMName $Command.VMName `
        -CommandId $Command.CommandId `
        -ScriptString $Command.Script

    # Aggregate all Messages into a single string
    $messages = $result.Value | ForEach-Object { $_.Message }
    $joined = ($messages -join "`n").Trim()

    # Detect error from result codes
    $hasError = $false
    
    if ($Command.CommandId -eq "RunPowerShellScript" -and $result.Value | Where-Object { $_.Code -match "StdErr" -and -not [string]::IsNullOrWhiteSpace($_.Message) }) {
        $hasError = $true
    }
    elseif ($Command.CommandId -eq "RunShellScript" ) {
        $message = $result.Value[0].Message
        
        if ($message -match "Error:" -or $message -match "error:") {
            $hasError = $true
        }
        
        $parts = $message -split "\[stderr\]"

        # If there's something after [stderr], treat it as error$res
        
        if ($parts.Count -gt 1 -and $parts[1].Trim().Length -gt 0) {
            $hasError = $true
        }
    }

    # Build and return structured PSObject
    $status = if ($hasError) { "error" } else { "success" }

    [PSCustomObject]@{
        status  = $status
        message = $joined
    }
}

function View-CommandResult {
    param(
        [Parameter(Mandatory = $true)]
        [pscustomobject]$Result
    )

    if ($Result.status -eq "error") {
        Write-Host $Result.message -ForegroundColor Red
    }
    else {
        Write-Host $Result.message
    }
}

function ExecuteCommands {
    
    $EnvironmentName = Get-EnvName -EnvironmentName $EnvironmentName

    #Get Vms with Given Caches
    $vms = GetVms -EnvironmentName $EnvironmentName

    #Check If VM Exists or not
    if (-not $vms -or $vms.Count -eq 0) {
        throw "No Server Exists To Create New Cache"
    }

    #Get Private IPs of Vms (VM Name, CachesList, NicName, PrivateIps, OsType)
    $privateIps = Get-VMPrivateIps -VMs $vms -ResourceGroupName $EnvironmentName

    #Get Cache Groups (CacheName, FirstVM, OsType, PrivateIps)
    $machine = Get-MachineInfo -VmInfo $privateIps

    #Get Commands to be Executed on Azure
    $command = Get-CommandForCache -machineInfo $machine -cacheName $Name
    
    #Execute Commands
    $cmdExecution = Invoke-CacheCommand -ResourceGroupName $EnvironmentName -Command $command
    
    if ($cmdExecution.status -eq "success") {
        Write-Output "Adding Tags"
        Add-CacheTagToVMs -ResourceGroupName $EnvironmentName -CacheName $Name -VMs $privateIps
    }
    
    View-CommandResult -Result $cmdExecution
}

try {
    if (-not (Get-AzContext)) {
        Connect-AzAccount
        if (Get-AzContext) {
            Write-Output "Created Context, Creating New Cache..."
            ExecuteCommands
        }
    }
    else {
        Write-Output "Already have Context, Creating New Cache..."
        ExecuteCommands
    }
}
catch {
    Write-Error $($_.Exception.Message)

    Write-Error "Coudn't Create New Cache'"
}