Cloud/Azure.ps1

# This funtion for using existing az contex if avalable else get new context from azure by browser autontication
function Connect-AzureAccount {
    param (
        [Parameter(Mandatory = $true)]
        [string]$TenantId,

        [Parameter(Mandatory = $true)]
        [string]$SubscriptionId,
        [switch]$Force
    )

    try {

        $currentContext = Get-AzContext
        if ($Force) {
            Write-GsLog "Forcefully. Initiating login..." Action
            $out = Connect-AzAccount -Tenant $TenantId -Subscription $SubscriptionId -ErrorAction Stop
            Write-GsLog "Connected to Azure with tenant $TenantId and subscription $SubscriptionId." Info
        }
        elseif ( (-not $currentContext) -or $currentContext.Tenant.Id -ne $TenantId  ) {
            Write-GsLog "No Azure context found. Initiating login..." Action
            $out = Connect-AzAccount -Tenant $TenantId -Subscription $SubscriptionId -ErrorAction Stop
            Write-GsLog "Connected to Azure with tenant $TenantId and subscription $SubscriptionId." Info
        }
        elseif ($currentContext.Tenant.Id -eq $TenantId -and $currentContext.Subscription.Id -ne $SubscriptionId) {
            Write-GsLog "Authenticated with correct tenant ($($currentContext.Tenant.Name)), but using a different subscription ($($currentContext.Subscription.Name))." Debug
            Write-GsLog "Switching Azure subscription to $SubscriptionId..." Action
            Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop
            $out = Get-AzContext
            Write-GsLog "Azure subscription context switched to $SubscriptionId." Info
        }
        else {
            Write-GsLog "Azure context already matches the specified tenant and subscription. Reusing current session." Info
        }
        return $out
    }
    catch {
        Write-GsLog "Failed to connect or set Azure context: $($_.Exception.Message)" Error
        exit 1
    }
}

# this function will get vms list and their disk
function Get-AzureVmAndDisk {
    param (
        [Parameter(Mandatory = $true)]
        [string[]]$VmNames
    )

    if (-not $VmNames -or $VmNames.Count -eq 0) {
        Write-GsLog "No VM names provided." Error
        return
    }

    Write-GsLog "Getting VM and disk list from Azure..." Info

    try {
        $vmList = Get-AzVM
        $diskList = Get-AzDisk

        Write-GsLog "Filtering VMs and their associated disks..." Debug

        $selectedVms = $vmList | Where-Object { $_.Name -in $VmNames }

        if (-not $selectedVms -or $selectedVms.Count -eq 0) {
            Write-GsLog "Unable to find any VMs matching the provided names: $($VmNames -join ', ')." Error
            return
        }

        $selectedVmIds = $selectedVms.Id
        $associatedDisks = $diskList | Where-Object { $_.ManagedBy -in $selectedVmIds }

        Write-GsLog "Found $($selectedVms.Count) VM(s) and $($associatedDisks.Count) associated disk(s)." Info

        return [PSCustomObject]@{
            VMs   = $selectedVms
            Disks = $associatedDisks
        }
    }
    catch {
        Write-GsLog "Failed to get VM or disk information: $($_.Exception.Message)" Error
        return
    }
}
# This function will help to get the vm information along with it's disk and Nic information
function Get-AzureVmFullInfo {
    param (
        [string[]]$VmName,
        [string]$ResourceGroupName
    )

    try {
        Write-GsLog "Fetching Azure VM details..." Info

        # Retrieve all VMs or filter by resource group
        $allVms = if ($ResourceGroupName) {
            Get-AzVM -ResourceGroupName $ResourceGroupName
        }
        else {
            Get-AzVM
        }

        # Filter by VM name if specified
        $filteredVms = if ($VmName) {
            $allVms | Where-Object { $_.Name -in $VmName }
        }
        else {
            $allVms
        }

        if (-not $filteredVms -or $filteredVms.Count -eq 0) {
            Write-GsLog "No matching VMs found." Warning
            return
        }

        $vmDetailsList = foreach ($vm in $filteredVms) {
            $vmStatus = Get-AzVM -ResourceGroupName $vm.ResourceGroupName -Name $vm.Name -Status

            # Get disk info
            $osDiskName = $vm.StorageProfile.OSDisk.Name
            $dataDiskNames = $vm.StorageProfile.DataDisks.Name
            $allDiskNames = @($osDiskName) + $dataDiskNames
            $disks = $allDiskNames | ForEach-Object {
                Get-AzDisk -ResourceGroupName $vm.ResourceGroupName -DiskName $_
            }

            # Get NICs
            $nicIds = $vm.NetworkProfile.NetworkInterfaces.Id
            $nics = $nicIds | ForEach-Object {
                Get-AzNetworkInterface -ResourceId $_
            }

            # Build NIC and IP details
            $nicDetails = foreach ($nic in $nics) {
                $ipConfigs = $nic.IpConfigurations | ForEach-Object {
                    [PSCustomObject]@{
                        PrivateIpAddress = $_.PrivateIpAddress
                        PublicIpAddress  = if ($_.PublicIpAddress) {
                            (Get-AzPublicIpAddress -ResourceGroupName $vm.ResourceGroupName -Name ($_.PublicIpAddress.Id.Split('/')[-1])).IpAddress
                        }
                        else { $null }
                        Subnet           = $_.Subnet.Id.Split('/')[-1]
                        Vnet             = $_.Subnet.Id.Split('/')[-3]
                    }
                }

                [PSCustomObject]@{
                    NicName   = $nic.Name
                    IpConfigs = $ipConfigs
                    Nsg       = if ($nic.NetworkSecurityGroup) {
                        (Get-AzNetworkSecurityGroup -ResourceGroupName $vm.ResourceGroupName -Name ($nic.NetworkSecurityGroup.Id.Split('/')[-1])).Name
                    }
                    else { $null }
                }
            }

            # Return full VM info
            [PSCustomObject]@{
                Name           = $vm.Name
                ResourceGroup  = $vm.ResourceGroupName
                Location       = $vm.Location
                VmSize         = $vm.HardwareProfile.VmSize
                OSType         = $vm.StorageProfile.OSDisk.OsType
                ComputerName   = $vm.OsProfile.ComputerName
                ProvisionState = $vmStatus.ProvisioningState
                PowerState     = ($vmStatus.Statuses | Where-Object { $_.Code -like 'PowerState/*' }).DisplayStatus
                Disks          = $disks
                Nics           = $nicDetails
            }
        }

        return $vmDetailsList
    }
    catch {
        Write-GsLog "Failed to retrieve VM details: $($_.Exception.Message)" Error
        return
    }
}


# This function will create snapshot from disk
function ConvertTo-AzureDiskToSnapshot {
    param (
        [Parameter(Mandatory = $true)]
        [Object[]]$Disk,

        [string]$Location = 'CentralIndia'
    )

    $OutputSnapshots = @()

    foreach ($currentDisk in $Disk) {
        $snapshotName = "snapshot_$($currentDisk.Name)_$(Get-Date -Format 'yyyy_MM_dd')_vhd"
        $resourceGroup = $currentDisk.ResourceGroupName

        try {
            # Check for existing snapshot
            $existingSnapshot = Get-AzSnapshot -SnapshotName $snapshotName -ResourceGroupName $resourceGroup -ErrorAction SilentlyContinue

            # Create snapshot config
            $snapshotConfig = New-AzSnapshotConfig -SourceUri $currentDisk.Id -Location $Location -CreateOption Copy

            if (-not $existingSnapshot) {
                Write-GsLog "Creating snapshot for disk '$($currentDisk.Name)' as '$snapshotName'" Action
                $newSnapshot = New-AzSnapshot -Snapshot $snapshotConfig -SnapshotName $snapshotName -ResourceGroupName $resourceGroup
            }
            else {
                Write-GsLog "Snapshot '$snapshotName' already exists. Updating it instead." Warning
                $newSnapshot = Update-AzSnapshot -Snapshot $existingSnapshot -SnapshotName $snapshotName -ResourceGroupName $resourceGroup -Verbose
            }

            $OutputSnapshots += $newSnapshot
        }
        catch {
            Write-GsLog "Failed to create or update snapshot for disk '$($currentDisk.Name)': $($_.Exception.Message)" Error
        }
    }

    return $OutputSnapshots
}





# This func will remove the snapshot which are provided
function Remove-AzureSnapshot {
    param (
        [Parameter(Mandatory = $true)]
        [object[]]$Snapshot
    )

    foreach ($snap in $Snapshot) {
        try {
            $snapName = $snap.Name
            $resourceGroup = $snap.ResourceGroupName

            Write-GsLog "Revoking access for snapshot '$snapName'" Info
            Revoke-AzSnapshotAccess -SnapshotName $snapName -ResourceGroupName $resourceGroup

            Write-GsLog "Deleting snapshot '$snapName' in resource group '$resourceGroup'" Action
            Remove-AzSnapshot -SnapshotName $snapName -ResourceGroupName $resourceGroup -Force

            Write-GsLog "Successfully deleted snapshot '$snapName'" Success
        }
        catch {
            Write-GsLog "Failed to delete snapshot '$($snap.Name)': $($_.Exception.Message)" Error
        }
    }
}

# To enable public assess of the snapshot
function Enable-AzureSnapshotPublicAccess {
    param (
        [Parameter(Mandatory = $true)]
        [string]$SnapshotName,

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

    try {
        Write-GsLog "Checking current network access setting for snapshot '$SnapshotName'" Info
        $snapshot = Get-AzSnapshot -SnapshotName $SnapshotName -ResourceGroupName $ResourceGroupName

        if ($snapshot.PublicNetworkAccess -eq 'Enabled') {
            Write-GsLog "Public network access is already enabled for snapshot '$SnapshotName'" Success
            return
        }

        Write-GsLog "Enabling public network access for snapshot '$SnapshotName'" Action

        $uri = "/subscriptions/$($snapshot.Id.Split('/')[2])/resourceGroups/$ResourceGroupName/providers/Microsoft.Compute/snapshots/$SnapshotName?api-version=2022-03-02"

        $payload = @{
            properties = @{
                publicNetworkAccess = 'Enabled'
            }
        } | ConvertTo-Json -Depth 5

        Invoke-AzRestMethod -Method PATCH -Path $uri -Payload $payload
    
        Write-GsLog "Public access successfully enabled for snapshot '$SnapshotName'" Success
    }
    catch {
        Write-GsLog "Failed to enable public access: $($_.Exception.Message)" Error
    }
}


# This function will help to send the snapshot to any storage account (even when inter Tenant)
function Send-AzureSnapshotsToStorage {
    param (
        [Parameter(Mandatory = $true)]
        [object[]]$Snapshot,

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

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

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

        [int]$TimeOutSec = 86400,

        [switch]$WaitForComplete
    )

    try {
        Write-GsLog "Creating storage context for '$StorageAccountName'" Info
        $context = New-AzStorageContext -StorageAccountName $StorageAccountName -StorageAccountKey $StorageAccountKey
    }
    catch {
        Write-GsLog "Failed to create storage context: $($_.Exception.Message)" Error
        return
    }

    $blobNames = @()

    foreach ($snap in $Snapshot) {
        try {
            $snapName = $snap.Name
            $resourceGroup = $snap.ResourceGroupName

            if ($snapshot.PublicNetworkAccess -ne 'Enabled') {
                Write-GsLog "Public network access is not enabled for snapshot '$($snapName)'" Warning
                Write-GsLog "Enabling public network access for snapshot '$( $snapName)'" Action
                Enable-AzureSnapshotPublicAccess -SnapshotName $snapName -ResourceGroupName $resourceGroup
            }
           
            Write-GsLog "Generating SAS URL for snapshot '$snapName'" Debug
            $sas = Grant-AzSnapshotAccess -ResourceGroupName $resourceGroup -SnapshotName $snapName -DurationInSecond $TimeOutSec -Access Read

            Write-GsLog "Starting blob copy for snapshot '$snapName' to container '$ContainerName'" Action
            $copyResult = Start-AzStorageBlobCopy -AbsoluteUri $sas.AccessSAS -DestContainer $ContainerName -DestContext $context -DestBlob $snapName

            $blobNames += $snapName
        }
        catch {
            Write-GsLog "Failed to start copy for snapshot '$($snap.Name)': $($_.Exception.Message)" Error
        }
    }

    if ($WaitForComplete) {
        Write-GsLog "Monitoring snapshot copy progress..." Info
        $inProgress = $true

        while ($inProgress) {
            $inProgress = $false

            foreach ($blobName in $blobNames) {
                $copyState = Get-AzStorageBlobCopyState -Blob $blobName -Container $ContainerName -Context $context

                if ($copyState.Status -ne "Success") {
                    $inProgress = $true
                    $percent = if ($copyState.TotalBytes -gt 0) {
                        [math]::Round(100 * $copyState.BytesCopied / $copyState.TotalBytes, 2)
                    }
                    else { 0 }

                    Write-GsLog "Snapshot '$blobName' copy status: $($copyState.Status) - $percent% completed" Debug
                }
                else {
                    Write-GsLog "Snapshot '$blobName' copy completed successfully" Success
                }
                Write-GsLog "`n`n`n" Debug
            }

            if ($inProgress) {
                Start-Sleep -Seconds 10
            }
        }
    }

    Write-GsLog "Fetching blob references from container '$ContainerName'" Info
    $blobs = @()
    foreach ($blobName in $blobNames) {
        try {
            $blob = Get-AzStorageBlob -Blob $blobName -Container $ContainerName -Context $context
            if ($blob) {
                $blobs += $blob.ICloudBlob
            }
        }
        catch {
            Write-GsLog "Failed to retrieve blob '$blobName': $($_.Exception.Message)" Warning
        }
    }

    return $blobs
}

function Invoke-AzureVmDiskToStorageAsSnapshotBlob {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [string]$TenantId = (Read-Host "Provide the source VM tenant ID"),

        [Parameter(Mandatory = $false)]
        [string]$SubscriptionId = (Read-Host "Provide the source VM subscription ID"),

        [Parameter(Mandatory = $false)]
        [string[]]$VmName = ((Read-Host "Provide source VM names (comma-separated)").Split(',') | ForEach-Object { $_.Trim() }),

        [Parameter(Mandatory = $false)]
        [string]$StorageAccountName = (Read-Host "Provide the destination Storage Account name"),

        [Parameter(Mandatory = $false)]
        [string]$ContainerName = (Read-Host "Provide the destination Container name"),

        [Parameter(Mandatory = $false)]
        [string]$StorageAccountKey = (Read-Host "Provide the destination Storage Account key")
    )
   
    try {
        Write-GsLog "Authenticating to Azure with Tenant: $TenantId and Subscription: $SubscriptionId" Action
        Connect-GsAzureAccount -TenantId $TenantId -SubscriptionId $SubscriptionId -Force
    }
    catch {
        Write-GsLog "Failed to authenticate to Azure: $($_.Exception.Message)" Error
        return
    }

    try {
        Write-GsLog "Fetching VM information for: $($VmName -join ', ')" Info
        $vmInfo = Get-GsAzureVmFullInfo -VmName $VmName
        if (-not $vmInfo -or -not $vmInfo.Disks) {
            Write-GsLog "No disks found for the specified VMs." Error
            return
        }
    }
    catch {
        Write-GsLog "Error fetching VM information: $($_.Exception.Message)" Error
        return
    }

    try {
        Write-GsLog "Creating snapshots from attached disks..." Info
        $snapshots = ConvertTo-GsAzureDiskToSnapshot -Disk $vmInfo.Disks
        if (-not $snapshots -or $snapshots.Count -eq 0) {
            Write-GsLog "No snapshots were created." Error
            return
        }
    }
    catch {
        Write-GsLog "Error creating snapshots: $($_.Exception.Message)" Error
        return
    }

    try {
        Write-GsLog "Starting snapshot copy to storage account '$StorageAccountName' in container '$ContainerName'" Info
        $blobs = Send-GsAzureSnapshotsToStorage -Snapshot $snapshots -StorageAccountName $StorageAccountName -ContainerName $ContainerName -StorageAccountKey $StorageAccountKey -WaitForComplete
        Write-GsLog "Snapshots successfully transferred. Total blobs: $($blobs.Count)" Success
    }
    catch {
        Write-GsLog "Snapshot copy failed: $($_.Exception.Message)" Error
    }

    try {
        Write-GsLog "Starting deletion of temporary snapshots..." Warning

        foreach ($snap in $snapshots) {
            Write-GsLog "Deleting snapshot '$($snap.Name)' in resource group '$($snap.ResourceGroupName)'" Action
        }

        Remove-GsAzureSnapshot -Snapshot $snapshots

        Write-GsLog "All snapshots successfully deleted." Success
    }
    catch {
        Write-GsLog "Snapshot deletion failed: $($_.Exception.Message)" Error
    }

    return $blobs
}



Export-ModuleMember  -Function '*'