frameworkResources/Scripts/start_cache.ps1

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

    [string]$Name,
    [string]$Servers,
    [int]$Port,
    [switch]$NoLogo,
    [PSCredential]$Credentials
)


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 Get-VmsWithCache{
    Param(
        [Parameter(Mandatory = $true)]
        [string]$EnvironmentName,

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

    #Filtering VMs using ResourceGroups and Cache Tags
    $vms = Get-AzVM -Status -ResourceGroupName $EnvironmentName -ErrorAction Stop  | Where-Object { $_.Tags.ContainsKey("Caches") }
    
    #Assign Cache Names Array to Vms
    $vms = foreach ($vm in $vms){
        $vm | Add-Member -NotePropertyName "CachesList" -NotePropertyValue ($vm.Tags["Caches"] -split "," | ForEach-Object { $_.Trim() }) -Force

        $vm
    }

    if($Name){
        # Split comma-separated list into array, trim spaces
        $requestedCaches = $Name.Split(",") | ForEach-Object { $_.Trim() } | Where-Object { $_ }

        $vms = foreach ($vm in $vms) {
            # Find intersection between requested and actual
            $matched = $vm.CachesList | Where-Object { $requestedCaches -contains $_ }
            if ($matched) {
                $vm.CachesList = $matched
                $vm
            }
        }
    }

    return $vms
}

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

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

        [string]$Servers
    )


    $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
                CachesList     = $vm.CachesList
                PrivateIps = ($nic.IpConfigurations | Select-Object -ExpandProperty PrivateIpAddress)
                OsType = $vm.StorageProfile.OsDisk.OsType
            }
        }
    }

    # If Servers is provided, validate and filter further
    # Servers should be subset of VMs, If VMs doesn't contain a name mentioned in servers error will be thrown.
    if ($Servers) {
        $serverList = $Servers -split "," | ForEach-Object { $_.Trim() }
        $privateIps = $VMPrivateIps.PrivateIps

        $missing = $serverList | Where-Object { $_ -notin $privateIps }
        if ($missing.Count -gt 0) {
            throw "One of the server doesn't contain NCache"
        }

        $VMPrivateIps = $VMPrivateIps | Where-Object { $_.PrivateIps -in $serverList }
    }


    return $VMPrivateIps
}

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

    $caches = foreach ($vm in $VMInfo) {
        foreach ($cache in $vm.CachesList) {
            [PSCustomObject]@{
                CacheName  = $cache
                VMName     = $vm.VMName
                PrivateIps = $vm.PrivateIps
                OsType     = $vm.OsType
            }
        }
    }

    $result = $caches | Group-Object -Property CacheName | ForEach-Object {
        [PSCustomObject]@{
            CacheName  = $_.Name
            FirstVM    = ($_.Group | Select-Object -First 1 -ExpandProperty VMName)
            OsType     = ($_.Group | Select-Object -First 1 -ExpandProperty OsType)
            PrivateIps = ($_.Group | Select-Object -ExpandProperty PrivateIps -Unique)
        }
    }

    return $result
}

function Get-LinuxCommand {
    param(
        [Parameter(Mandatory = $true)]
        $cacheInfo
    )

    $ipList = ($cacheInfo.PrivateIps -join ",")
    $command = "/opt/ncache/bin/tools/start-cache $($cacheInfo.CacheName) -server `"$ipList`""

    if ($Port)      { $command += " -port $Port" }
    if ($NoLogo)    { $command += " -nologo" }
    if ($Credentials) { 
        # credentials aren’t usually passed like this in Linux,
        # but let's say we format them
        $command += " -userid $($Credentials.UserName)"
    }

    return $command
}

function Get-WindowsCommand {
    param(
        [Parameter(Mandatory = $true)]
        $cacheInfo
    )

    $ipList = ($cacheInfo.PrivateIps -join ",")
    $command = "Start-Cache -Name $($cacheInfo.CacheName) -Server `"$ipList`""

    if ($Port)      { $command += " -Port $Port" }
    if ($NoLogo)    { $command += " -NoLogo" }
    if ($Credentials) { 
        $command += " -UserName $($Credentials.UserName)"
        # you usually wouldn’t echo the password in plain text
        # (NCache would expect a secure string or prompt)
    }

    return $command
}

function Get-CommandForCache {
    param (
        [Parameter(Mandatory=$true)]
        $cacheInfo
    )

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

function Get-Commands {
    Param(
        [Parameter(Mandatory=$true)]
        $cacheGroups
    )

    $commands = foreach($cacheInfo in $cacheGroups){
        Get-CommandForCache -cacheInfo $cacheInfo
    }

    return $commands
}

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 Invoke-CacheCommandList {
    param(
        [Parameter(Mandatory = $true)]
        [string]$ResourceGroupName,

        [Parameter(Mandatory = $true)]
        [array]$Commands
    )

    foreach ($cmd in $Commands) {
        Invoke-CacheCommand `
            -ResourceGroupName $ResourceGroupName `
            -Command $cmd
    }
}

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 = Get-VmsWithCache -EnvironmentName $EnvironmentName -Name $Name

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

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

    #Get Cache Groups (CacheName, FirstVM, OsType, PrivateIps)
    $cacheGroups = Get-CacheGrouping -VMInfo $privateIps

    #Get Commands to be Executed on Azure
    $commands = Get-Commands -cacheGroups $cacheGroups
    
    #Execute Commands
    $results = Invoke-CacheCommandList -ResourceGroupName $EnvironmentName -Commands $commands

    $results | ForEach-Object { View-CommandResult -Result $_ }

}

try {
    if (-not (Get-AzContext)) {
        Connect-AzAccount
        if (Get-AzContext) {
            Write-Output "Starting Cache..."
            ExecuteCommands
        }
    }
    else {
        Write-Output "Starting Cache..."
        ExecuteCommands
    }
}
catch {
    Write-Error $($_.Exception.Message)

    Write-Error "Coudn't Start Cache'"
}