framework/Resources/Scripts/common.ps1

function GetUploadedVms {
    param(
        [string]$ResourceGroup
    )
    $vms = @(Get-AzVM -ResourceGroupName $ResourceGroup | 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
            }
        })
    return $vms
}

function RemoveResources {
    param(
        [string]$VmName,
        [string]$DiskName,
        [string]$NetworkInterfaceName,
        [string]$PublicIpName,
        [string]$ResourceGroup,
        [bool]$UseForce
    )
    if ($UseForce) {
        Remove-AzVM -ResourceGroupName $ResourceGroup -Name $VmName -Force
        Remove-AzDisk -ResourceGroupName $ResourceGroup -DiskName $DiskName -Force
        Remove-AzNetworkInterface -Name $NetworkInterfaceName -ResourceGroupName $ResourceGroup -Force
        Remove-AzPublicIpAddress -Name $PublicIpName -ResourceGroupName $ResourceGroup -Force
    }
    else {
        Remove-AzVM -ResourceGroupName $ResourceGroup -Name $VmName
        Remove-AzDisk -ResourceGroupName $ResourceGroup -DiskName $DiskName
        Remove-AzNetworkInterface -Name $NetworkInterfaceName -ResourceGroupName $ResourceGroup
        Remove-AzPublicIpAddress -Name $PublicIpName -ResourceGroupName $ResourceGroup
    }

}


function CreateStartupCommand {
    param(
        [string]$Key,
        [string]$firstName,
        [string]$lastName,
        [string]$email,
        [string]$company,
        [string]$environment
    )
    $cloudType = "azs"
    $StartupScriptUrl = "https://ncachedeployments.s3.us-east-1.amazonaws.com/5.3.5-staging/StartupScriptLinux.sh"
    return "
        #!/bin/sh
        sleep 30;wget -O /tmp/StartupScriptLinux.sh $StartupScriptUrl;chmod +x /tmp/StartupScriptLinux.sh;/tmp/StartupScriptLinux.sh -InstallType $cloudType
        -Key $Key -FirstName $firstName -LastName $lastName -Email $email -Company $company -Environment $environment
    "
 
}
 

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 = @()
    }

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

    # Regex checks (multiple spaces or missing commas)
    $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 GetVmSize {
    param(
        [string]$VmSize
    )
    $dict = @{
        "ENT-1/1"    = "Standard_B1ms"
        "ENT-4/2"    = "Standard_D2ls_v5"
        "ENT-8/2"    = "Standard_D2as_v5"
        "ENT-8/4"    = "Standard_D4ls_v5"
        "ENT-16/2"   = "Standard_E2as_v5"
        "ENT-16/4"   = "Standard_D4as_v5"
        "ENT-16/8"   = "Standard_D8ls_v5"
        "ENT-32/4"   = "Standard_E4as_v5"
        "ENT-32/8"   = "Standard_D8as_v5"
        "ENT-32/16"  = "Standard_D16ls_v5"
        "ENT-64/8"   = "Standard_E8as_v5"
        "ENT-64/16"  = "Standard_D16as_v5"
        "ENT-64/32"  = "Standard_D32ls_v5"
        "ENT-128/16" = "Standard_E16as_v5"
        "ENT-128/32" = "Standard_D32as_v5"
        "ENT-128/64" = "Standard_D64ls_v5"
        "ENT-256/32" = "Standard_E32as_v5"
        "ENT-256/64" = "Standard_D64as_v5"
    }

    return $dict[$VmSize]
}


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($LicenseKey)) {
        throw "License Key is required"
    }

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

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

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

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

    $output = GetVmSize -VmSize $VmSizeName

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

}

function GetNCacheServers {
    param(
        [string]$rgName
    )
    $vms = @(Get-AzVM -ErrorAction Stop -ResourceGroupName $rgName  | Where-Object { $_.Tags.ContainsKey("ServerType") -and $_.Tags["ServerType"] -eq "NCache" -and $_.Tags.ContainsKey("InstallMode") -and $_.Tags.ContainsKey("InstallMode") -eq "server" }  | ForEach-Object {
            $nicId = $_.NetworkProfile.NetworkInterfaces[0].Id
            $nicName = ($nicId -split "/")[-1]
            $nic = Get-AzNetworkInterface -ResourceGroupName $rgName -Name $nicName -ErrorAction Stop 
            $macAddress = $nic.MacAddress  # <-- added this line
            $privateIp = $nic.IpConfigurations[0].PrivateIpAddress
            [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
                PrivateIp          = $privateIp
                MacAddress         = $macAddress          
            }
        }  )

    return , $vms
}

function CreateScriptBlockLinux {
    param(
        [string]$firstName,
        [string]$lastName,
        [string]$email,
        [string]$evaluationKey,
        [string]$company,
        [string[]]$privateIps,
        [string]$setupUrl,
        [string]$licenseKey,
        [string]$environmentName,
        [string]$enableCacheServerIp,
        [string]$licenseDuration,
        [string]$installNCacheDir
    )
    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 '$company' -PrivateIps $privateIps -SetupUrl '$setupUrl' -LicenseKey '$licenseKey' -Environment '$environmentName' -EnableCacheServerPublicIp $enableCacheServerIp -LicenseDuration '$licenseDuration'"

}

function CreateScriptBlockWindows {
    param(
        [string]$InstallNCacheDir,
        [string]$EvaluationKey,
        [string]$UserName,
        [string]$CompanyName,
        [string]$FirstName,
        [string]$LastName,
        [string]$Password,
        [string]$Email,
        [string[]]$PrivateIps,
        [string]$SetupUrl,
        [string]$LicenseKey,
        [string]$EnableCacheServerIp,
        [string]$EnvironmentName,
        [string]$LicenseDuration
    )

    $commaSeparatedIps = $PrivateIps -join ','

    return @"
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 '$commaSeparatedIps' -SetupUrl '$SetupUrl' -LicenseKey '$LicenseKey' -EnableCacheServerPublicIp `$$EnableCacheServerIp -Environment '$EnvironmentName' -LicenseDuration '$LicenseDuration'
"@

}




function DownloadAndInstallNCache {
    param(
        [string]$ResourceGroupName,
        [string[]]$Servers,
        [string]$Command,
        [string]$OS
    )

    foreach ($server in $arrayList) {
        Write-Host "$((Get-Date).ToString('yyyy-MM-dd HH:mm:ss')) - Downloading and Installing NCache on $server"
    }
    $jobs = foreach ($server in $Servers) {
        Start-Job -ScriptBlock {
            param($rg, $vm, $script, $vmsOs)
            if ($vmsOs -eq "Linux") {
                Invoke-AzVMRunCommand -ResourceGroupName $rg -VMName $vm -CommandId 'RunShellScript' -ScriptString $script
            }
            else {
                Invoke-AzVMRunCommand -ResourceGroupName $rg -VMName $vm -CommandId 'RunPowerShellScript' -ScriptString $script
            }
            Write-Host "$((Get-Date).ToString('yyyy-MM-dd HH:mm:ss')) - Installed NCache on server $vm"

        }  -ArgumentList $ResourceGroupName, $server, $Command, $OS
    }

    $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
    }

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

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

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 EnableCacheServerPublicIp {
    param(
        [string]$RuleType
    )
    if ($RuleType -eq "Data" -or $RuleType -eq "All") {
        return $true
    }
    return $false
}

function ShowDeployedServersWithLink {
    param(
        [string]$rg
    )
    $vms = Get-AzVM -ResourceGroupName $rg -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 $rg -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 $rg -Name $publicIpName
            $publicIp = $publicIpObj.IpAddress

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

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

    $results | Format-Table -AutoSize
}

function CheckIfResourceGroupCreatedByUsExists {
    param(
        [string]$EnvironmentName
    )
    $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"
    }
    return $resource
}

function CheckIfUserResourceGroupAlreadyExists {
    param(
        [string]$EnvironmentName
    )
    $resource = Get-AzResourceGroup -Name $EnvironmentName -ErrorAction SilentlyContinue
    return $resource
}

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 CheckIfEnvironmentExits {
    param(
        [string]$Environment
    )
    $resourceGroup = Get-AzResourceGroup -ErrorAction Stop | Where-Object { $_.Tags -and $_.Tags.Contains("EnvironmentName") -and $_.Tags["EnvironmentName"] -eq $Environment }
    if (-not $resourceGroup) {
        throw "The specified environment '$Environment' does not exist. Use Get-NCAzEnvironment to list available environments and try again"
    }
    return $resourceGroup
}

function ShowExecutingCommand {
    Write-Host "$((Get-Date).ToString('yyyy-MM-dd HH:mm:ss')) - Executing command"
}

function TimeStatus {
    param(
        [string]$OS
    )

    if ($OS -eq "Windows") {
        Write-Host "Please wait the deployment can take upto 9 to 14 minutes"
    }
    else {
        Write-Host "Please wait the deployment can take upto 8 to 12 minutes"
    }
}

function Get-LinuxNCacheInstallDir {
    param(
        [Parameter(Mandatory = $true)]
        [string]$VMName,

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

    $Path = $null

    try {
        # 1. Fetch VM and check its tags
        $vm = Get-AzVM -Name $VMName -ResourceGroupName $ResourceGroupName -ErrorAction Stop
        if ($vm.Tags.ContainsKey("InstallNCacheDir") -and $vm.Tags["InstallNCacheDir"]) {
            $Path = $vm.Tags["InstallNCacheDir"]
        }
        else {
            Write-Host "No 'InstallNCacheDir' tag found on VM '$VMName'. Using default '/opt/ncache'." -ForegroundColor Yellow
            $Path = "/opt/ncache"
        }
    }
    catch {
        Write-Warning "Failed to fetch VM info for '$VMName'. Using default '/opt/ncache'. $_"
        $Path = "/opt/ncache"
    }

    # 2. Normalize slashes (Linux default)
    $Path = $Path -replace '\\', '/'

    # 3. Ensure path ends with 'ncache'
    if ($Path -notmatch "(?i)ncache$") {
        $Path = (Join-Path $Path "ncache")
    }

    return $Path
}


function CreateCmdletLinux {
    param(
        [string]$NCacheInstallDir,
        [string]$Cmdlet
    )

    return "$NCacheInstallDir/bin/tools/$CmdLet"
}

function InvokeCommandOnVms {
    param(
        [string]$ResourceGroupName,
        [string]$server,
        [string]$Command,
        [string]$OS
    )

    $result = $null
    if ($OS -eq "Linux") {
        $result = Invoke-AzVMRunCommand -ResourceGroupName $ResourceGroupName -VMName $server -CommandId 'RunShellScript' -ScriptString $Command
    }
    else {
        $result = Invoke-AzVMRunCommand -ResourceGroupName $ResourceGroupName -VMName $server -CommandId 'RunPowerShellScript' -ScriptString $Command
    }

    $messages = $result.Value | ForEach-Object { $_.Message }
    $joined = ($messages -join "`n").Trim()

    $hasError = $false
    
    $stdErr = ($result.Value | Where-Object { $_.Code -match "StdErr" }).Message

    if ($OS -eq "Windows" -and -not [string]::IsNullOrWhiteSpace($stdErr)) {
        $hasError = $true
    }
    elseif ($OS -eq "Linux" ) {
        $message = $result.Value[0].Message
        
        if ($message -match "Error:" -or $message -match "error:") {
            $hasError = $true
        }
        
        $parts = $message -split "\[stderr\]"
        
        if ($parts.Count -gt 1 -and $parts[1].Trim().Length -gt 0) {
            $hasError = $true
        }
    }
    $status = if ($hasError) { "error" } else { "success" }
    [PSCustomObject]@{
        status  = $status
        message = $joined
    }
}

function GetVmOSType {
    param(
        [string]$ResourceGroupName,
        [string]$ServerName
    )
    $vm = Get-AzVM -ResourceGroupName $script:resourceGroupName -Name $ServerName -ErrorAction Stop
    $vmOS = $vm.StorageProfile.OsDisk.OsType
    return $vmOS
}

function GetVmNamesFromPrivateIps {
    param(
        [string]$PrivateIp,
        [string]$ResourceGroupName
    )

    $vms = Get-AzVM -ResourceGroupName $ResourceGroupName -Status | Where-Object { $_.Tags.ContainsKey("InstallMode") -and $_.Tags["InstallMode"] -eq "server" }
    foreach ($vm in $vms) {
        $nicId = $vm.NetworkProfile.NetworkInterfaces[0].Id
        $nicName = ($nicId -split '/')[8]
        $nic = Get-AzNetworkInterface -ResourceGroupName $global:nc_resource_group -Name $nicName
        $vmPrivateIp = $nic.IpConfigurations[0].PrivateIpAddress
        if ($PrivateIp -eq $vmPrivateIp) {
            return $vm.Name
        }
    }
}

function GetVmDetailsFromPrivateIp {
    param(
        [string]$rgName,
        [string]$PrivateIp
    )
    $vms = @(Get-AzVM -ErrorAction Stop -ResourceGroupName $rgName  | Where-Object { $_.Tags.ContainsKey("ServerType") -and $_.Tags["ServerType"] -eq "NCache" -and $_.Tags.ContainsKey("InstallMode") -and $_.Tags["InstallMode"] -eq "server" } | ForEach-Object {
            $nicId = $_.NetworkProfile.NetworkInterfaces[0].Id
            $nicName = ($nicId -split "/")[-1]
            $nic = Get-AzNetworkInterface -ResourceGroupName $rgName -Name $nicName -ErrorAction Stop 
            $private = $nic.IpConfigurations[0].PrivateIpAddress
            [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
                PrivateIp          = $private
            }
        }  )

    foreach ($vm in $vms) {
        if ($vm.PrivateIp -eq $PrivateIp) {
            return $vm
        }
    }
    throw "Invalid private ip provided"
}
function GetNCacheServers {
    param(
        [string]$rgName
    )
    $vms = @(Get-AzVM -ErrorAction Stop -ResourceGroupName $rgName  | Where-Object { $_.Tags.ContainsKey("ServerType") -and $_.Tags["ServerType"] -eq "NCache" -and $_.Tags.ContainsKey("InstallMode") -and $_.Tags["InstallMode"] -eq "server" } | ForEach-Object {
            $nicId = $_.NetworkProfile.NetworkInterfaces[0].Id
            $nicName = ($nicId -split "/")[-1]
            $nic = Get-AzNetworkInterface -ResourceGroupName $rgName -Name $nicName -ErrorAction Stop 
            $macAddress = $nic.MacAddress  # <-- added this line
            $privateIp = $nic.IpConfigurations[0].PrivateIpAddress
            [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
                PrivateIp          = $privateIp
                MacAddress         = $macAddress
            }
        }  )
    return , $vms
}

function GetNCacheActivatedServers {
    param(
        [string]$rgName
    )
    $vms = @(Get-AzVM -ErrorAction Stop -ResourceGroupName $rgName  | Where-Object { $_.Tags.ContainsKey("ServerType") -and $_.Tags["ServerType"] -eq "NCache" -and $_.Tags.ContainsKey("InstallMode") -and $_.Tags["InstallMode"] -eq "server" -and $_.Tags.ContainsKey("NCacheActivated") -and $_.Tags["NCacheActivated"] -eq "True" } | ForEach-Object {
            $nicId = $_.NetworkProfile.NetworkInterfaces[0].Id
            $nicName = ($nicId -split "/")[-1]
            $nic = Get-AzNetworkInterface -ResourceGroupName $rgName -Name $nicName -ErrorAction Stop 
            $privateIp = $nic.IpConfigurations[0].PrivateIpAddress
            [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
                PrivateIp          = $privateIp
            }
        }  )
    return , $vms
}



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

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

    # Script that will execute inside the VM
    $script = @'
$appName = "*NCache*"
$app = Get-ItemProperty "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*" , `
    "HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" | `
    Where-Object { $_.DisplayName -like $appName }
 
if ($app) {
    $app.UninstallString -match '{.*}' | Out-Null
    $productCode = $Matches[0]
    Write-Output "Uninstalling product with code: $productCode"
    Start-Process msiexec.exe -ArgumentList "/x $productCode /quiet /norestart" -Wait
 
    # Remove leftover registry entries
    if (Test-Path "HKLM:\SOFTWARE\Alachisoft") {
        Remove-Item -Path "HKLM:\SOFTWARE\Alachisoft" -Recurse -Force
        Write-Output "Removed registry key HKLM:\SOFTWARE\Alachisoft"
    }
    else {
        Write-Output "Registry key not found"
    }
}
else {
    Write-Output "No matching NCache installation found."
}
'@

    try {
        $jobs = @()

        foreach ($vmNameToUn in $VMNames) {
            $jobs += Start-Job -ScriptBlock {
                param($rg, $vm, $scriptText)

                try {
                    Write-Output "Executing uninstall script on VM '$vm' in resource group '$rg'"
                    $result = Invoke-AzVMRunCommand `
                        -ResourceGroupName $rg `
                        -VMName $vm `
                        -CommandId 'RunPowerShellScript' `
                        -ScriptString $scriptText

                    # Print output from inside the VM
                    $result.Value | ForEach-Object { Write-Output $_.Message }
                }
                catch {
                    Write-Error "Failed to uninstall NCache on VM '$vm'. Error: $_"
                }
            } -ArgumentList $ResourceGroupName, $vmNameToUn, $script
        }
        # Print output from inside the VM
        $result.Value | ForEach-Object { Write-Output $_.Message }
               
        $jobs | Wait-Job | Out-Null
        $jobs | Receive-Job

    }
    catch {
        Write-Error "Failed to uninstall NCache on VM '$VMName'. Error: $_"
    }

}


function ActivatedNCache {
    param(
        [string]$UserProvidedKey,
        $resource
    )
    if (![string]::IsNullOrEmpty($UserProvidedKey) -or ($null -ne $resource -and $resource.Tags.ContainsKey("LicenseKey") -and -not [string]::IsNullOrEmpty($resource.Tags["LicenseKey"]))) {
        return $true
    }
    return $false
}

function Uninstall-NCacheFromVMLinux {
    param(
        [Parameter(Mandatory = $true)]
        [string]$ResourceGroupName,
        [Parameter(Mandatory = $true)]
        [string[]]$VMNames
    )

    $cmd = "/tmp/ncache-enterprise-dotnet/uninstall -F"
    try {

        $jobs = @()
        foreach ($vmNameToUn in $VMNames) {
            $jobs += Start-Job -ScriptBlock {
                param($rg, $vm, $scriptText)

                try {
                    $result = Invoke-AzVMRunCommand `
                        -ResourceGroupName $rg `
                        -VMName $vm `
                        -CommandId 'RunShellScript' `
                        -ScriptString $scriptText

                    # Print output from inside the VM
                    $result.Value | ForEach-Object { Write-Output $_.Message }
                }
                catch {
                    Write-Error "Failed to uninstall NCache on VM '$vm'. Error: $_"
                }
            } -ArgumentList $ResourceGroupName, $vmNameToUn, $cmd
        }
        # Print output from inside the VM
        $result.Value | ForEach-Object { Write-Output $_.Message }
       
        $jobs | Wait-Job | Out-Null
        $jobs | Receive-Job

    }
    catch {
        Write-Error "Failed to uninstall NCache on VM '$VMName'. Error: $_"
    }

}

function GetVmMacAddress {
    param(
        [string]$RgName,
        [string]$VmName
    )
    $vm = Get-AzVM -ResourceGroupName $RgName -Name $VmName
    $vm.NetworkProfile.NetworkInterfaces
    $nicId = $vm.NetworkProfile.NetworkInterfaces[0].Id
    $nicName = ($nicId -split "/")[-1]
    $macAdress = (Get-AzNetworkInterface -Name $nicName -ResourceGroupName "<yourRG>").MacAddress
    return $macAdress
}

function NoContext {
    Write-Warning "Please login to Azure using Connect-AzAccount"
}

function ResourceGroupLink {
    param(
        [string]$RgName
    )
    $context = (Get-AzContext)
    
    $tenantName = $context.Tenant.Name
    $subscriptionId = $context.Subscription.Id
    $link = "https://portal.azure.com/#@$tenantName/resource/subscriptions/$subscriptionId/resourceGroups/$RgName/overview"

    $list = [PSCustomObject]@{
        Link = $link
    }

    $list | Format-List
}

function GetVmsWithPublicIp {
    param(
        [string]$ResourceGroupName
    )
    $vmsObject = @(Get-AzVM -ResourceGroupName $ResourceGroupName |
        Where-Object { $_.Tags.ContainsKey("ServerType") -and $_.Tags["ServerType"] -eq "NCache" } | ForEach-Object {
            
            $nicId = $_.NetworkProfile.NetworkInterfaces[0].Id
            $nicName = ($nicId -split "/")[-1]
            
            $nic = Get-AzNetworkInterface -ResourceGroupName $ResourceGroupName -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 $ResourceGroupName -Name $publicIpName
                $publicIp = $publicIpObj.IpAddress

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

            [PSCustomObject]@{
                Name           = $_.Name
                Location       = $_.Location
                NIC            = $nicName
                PrivateIp      = $privateIp
                PublicIp       = $publicIp
                ManagementLink = $managementLink
            }
        } -ErrorAction Stop)
    return $vmsObject
}

function InvokeCommandOnMultipleVms {
    param(
        [PSCustomObject[]]$VmList,
        [string]$ResourceGroupName,
        [string]$OS
    )
    
    $jobs = foreach ($vm in $VmList) {
        if ($OS -eq "Windows") {
            Start-Job -ScriptBlock {
                param($rg, $vm, $script)

                $result = Invoke-AzVMRunCommand -ResourceGroupName $rg -VMName $vm -CommandId 'RunPowerShellScript' -ScriptString $script
                $messages = $result.Value | ForEach-Object { $_.Message }
                $joined = ($messages -join "`n").Trim()
                $hasError = $false
                $stdErr = ($result.Value | Where-Object { $_.Code -match "StdErr" }).Message

                if ($OS -eq "Windows" -and -not [string]::IsNullOrWhiteSpace($stdErr)) {
                    $hasError = $true
                }
                $status = if ($hasError) { "error" } else { "success" }

                if ($status -eq "error") {
                    Write-Host $joined -ForegroundColor Red
                }
                else {
                    Write-Host $joined
                }
            } -ArgumentList $ResourceGroupName, $vm.Name, $vm.Command
        }
        else {
            Start-Job -ScriptBlock {
                param($rg, $vm, $script)
                $result = Invoke-AzVMRunCommand -ResourceGroupName $rg -VMName $vm -CommandId 'RunShellScript' -ScriptString $script
                $messages = $result.Value | ForEach-Object { $_.Message }
                $joined = ($messages -join "`n").Trim()
                $message = $result.Value[0].Message
                $hasError = $false
                if ($message -match "Error:" -or $message -match "error:") {
                    $hasError = $true
                }
        
                $parts = $message -split "\[stderr\]"
        
                if ($parts.Count -gt 1 -and $parts[1].Trim().Length -gt 0) {
                    $hasError = $true
                }
                $status = if ($hasError) { "error" } else { "success" }
                if ($status -eq "error") {
                    Write-Host $joined -ForegroundColor Red
                }
                else {
                    Write-Host $joined
                }
            } -ArgumentList $ResourceGroupName, $vm.Name, $vm.Command
        }
    }

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

function CheckIfAllVmsAreRunning {
    param(
        [string]$ResourceGroup
    )

    $vmList = Get-AzVM -ResourceGroupName $ResourceGroup -Status
    $running = $true
    foreach ($vm in $vmList) {
        if ($vm.PowerState -ne "VM running") {
            $running = $false
        }
    }
    return $running
}

function CheckIfVmIsRunning {
    param(
        [string]$ResourceGroup,
        [string]$VmName
    )
    $running = $true
    $vm = Get-AzVM -ResourceGroupName $ResourceGroup -Name $VmName -Status

    $powerState = ($vm.Statuses | Where-Object { $_.Code -like "PowerState/*" }).DisplayStatus
    if ($powerState -ne "VM running") {
        $running = $false
    }
    return $running
}