Public/AzStackHci.PrivateLinkScope.ps1

# ////////////////////////////////////////////////////////////////////////////

# ////////////////////////////////////////////////////////////////////////////
# Function to test if an Azure Arc Machine has a Private Link Scope enabled.
# Returns $true if a Private Link Scope is enabled, otherwise returns $false.
Function Test-ArcMachinePrivateLinkScopeEnabled {
    <#
    .SYNOPSIS
        Tests if an Azure Arc Machine has a Private Link Scope enabled.
     
    .DESCRIPTION
        This function authenticates using Managed Identity and checks if the specified
        Azure Arc Machine has a Private Link Scope enabled. The function reads the
        Arc resource ID from the METRICS_ARC_RESOURCE_URI environment variable.
     
    .PARAMETER ArcResourceId
        Optional. The Azure Resource ID of the Arc Machine. If not provided, the function
        will use the METRICS_ARC_RESOURCE_URI environment variable.
     
    .EXAMPLE
        Test-ArcMachinePrivateLinkScopeEnabled
        Tests if the Azure Arc Machine specified in the METRICS_ARC_RESOURCE_URI environment
        variable has a Private Link Scope enabled. Returns $true if a Private Link Scope is found.
     
    .EXAMPLE
        Test-ArcMachinePrivateLinkScopeEnabled -ArcResourceId "/subscriptions/.../resourceGroups/rg-001/providers/Microsoft.HybridCompute/machines/server01"
        Tests a specific Arc Machine for Private Link Scope configuration.
     
    .OUTPUTS
        System.Boolean
        Returns $true if Private Link Scope is enabled, $false otherwise.
     
    .NOTES
        Requires Az.Accounts and Az.ConnectedMachine modules.
    #>

    
    [CmdletBinding()]
    [OutputType([bool])]
    param (
        [Parameter(Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
        [string]$ArcResourceId,

        [Parameter(Mandatory=$false, HelpMessage="Optional switch to prevent console output from the function.")]
        [switch]$NoOutput
    )

    begin {
        # Handle -NoOutput: suppress all console output
        if ($NoOutput.IsPresent) {
            $script:SilentMode = $true
        }

        # Write-Debug "Test-ArcMachinePrivateLinkScopeEnabled: Beginning Private Link Scope check for Azure Arc Machine"
        
        # Set verbose preference if -Verbose switch is used
        if ($script:SilentMode) {
            $VerbosePreference = 'SilentlyContinue'
            $DebugPreference = 'SilentlyContinue'
        } else {
            if ($PSBoundParameters['Verbose']) {
                $VerbosePreference = 'Continue'
            }
            if ($PSBoundParameters['Debug']) {
                $DebugPreference = 'Continue'
            }
        }
    }

    process {
        try {
            # Check if already authenticated to Azure
            Write-Verbose "Checking Azure authentication context..."
            $context = Get-AzContext -ErrorAction SilentlyContinue
            
            if (-not $context) {
                Write-Verbose "No existing Azure context found. Authenticating with Managed Identity..."
                try {
                    Connect-AzAccount -Identity -ErrorAction Stop | Out-Null
                    Write-Verbose "Successfully authenticated using Managed Identity."
                } catch [System.Management.Automation.CommandNotFoundException] {
                    Write-Error "Az.Accounts module not found. Install it with: Install-Module Az.Accounts"
                    throw "Required module Az.Accounts is not installed."
                } catch {
                    # Provide actionable guidance for common Managed Identity failures:
                    # - Identity not enabled on the VM/resource
                    # - Identity doesn't have required RBAC role assignments
                    # - Token endpoint not reachable (network/firewall issue)
                    $errMsg = $_.Exception.Message
                    if ($errMsg -match 'IMDS|metadata|169\.254\.169\.254') {
                        Write-Error "Managed Identity metadata endpoint unreachable. Ensure the VM has a System or User Assigned Managed Identity enabled and that IMDS (169.254.169.254) is accessible."
                    } elseif ($errMsg -match 'token|unauthorized|forbidden') {
                        Write-Error "Managed Identity token acquisition failed. Verify the identity has Reader role on the Arc resource. Details: $errMsg"
                    } else {
                        Write-Error "Failed to authenticate with Managed Identity: $errMsg"
                    }
                    throw "Unable to authenticate to Azure. Ensure Managed Identity is enabled and has appropriate permissions."
                }
            } else {
                Write-Verbose "Using existing Azure context: $($context.Account.Id)"
            }
            
            # Get Arc Resource ID from parameter or environment variable
            if (-not $PSBoundParameters.ContainsKey('ArcResourceId')) {
                Write-Verbose "No ArcResourceId parameter provided. Checking METRICS_ARC_RESOURCE_URI environment variable..."
                $ArcResourceId = $Env:METRICS_ARC_RESOURCE_URI
            }
            
            # Validate Arc Resource ID is set
            if ([string]::IsNullOrWhiteSpace($ArcResourceId)) {
                $errorMessage = "Arc Resource ID not provided. Either specify the -ArcResourceId parameter or set the METRICS_ARC_RESOURCE_URI environment variable."
                Write-Error $errorMessage
                throw $errorMessage
            }
            
            Write-Verbose "Arc Resource ID: $ArcResourceId"
            
            # Parse resource group and machine name from Arc Resource ID
            # Expected format: /subscriptions/{guid}/resourceGroups/{rg-name}/providers/Microsoft.HybridCompute/machines/{machine-name}
            if ($ArcResourceId -match "resourceGroups/([^/]+)/.*machines/([^/]+)") {
                $resourceGroup = $matches[1]
                $resourceName  = $matches[2]
                
                Write-Verbose "Parsed Resource Group: $resourceGroup"
                Write-Verbose "Parsed Machine Name: $resourceName"
                
                # Retrieve the Arc Machine and check for Private Link Scope
                Write-Verbose "Retrieving Azure Arc Machine configuration..."
                try {
                    $arcMachine = Get-AzConnectedMachine -Name $resourceName -ResourceGroupName $resourceGroup -ErrorAction Stop
                    
                    if (-not $arcMachine) {
                        Write-Error "Arc Machine '$resourceName' not found in Resource Group '$resourceGroup'."
                        return $false
                    }
                    
                    $PrivateLinkScopeResourceId = $arcMachine.PrivateLinkScopeResourceId
                    
                } catch {
                    Write-Error "Failed to retrieve Azure Arc Machine '$resourceName' in Resource Group '$resourceGroup': $($_.Exception.Message)"
                    throw
                }
                
                # Check if Private Link Scope is configured
                if (-not [string]::IsNullOrWhiteSpace($PrivateLinkScopeResourceId)) {
                    Write-Warning "Private Link Scope is ENABLED for Arc Machine '$resourceName'."
                    Write-Warning "Private Link Scope Resource ID: $PrivateLinkScopeResourceId"
                    return $true
                } else {
                    Write-HostAzS "No Private Link Scope found for Arc Machine '$resourceName' in Resource Group '$resourceGroup'." -ForegroundColor Green
                    Write-Verbose "Private Link is NOT enabled - machine is using public endpoints."
                    return $false
                }
                
            } else {
                $errorMessage = "Invalid Arc Resource ID format: $ArcResourceId. Expected format: /subscriptions/{guid}/resourceGroups/{rg-name}/providers/Microsoft.HybridCompute/machines/{machine-name}"
                Write-Error $errorMessage
                throw $errorMessage
            }
            
        } catch {
            Write-Error "Failed to check Private Link Scope status: $($_.Exception.Message)"
            Write-Debug "Stack Trace: $($_.ScriptStackTrace)"
            throw $_
        }
    } # End of process block
    
    end {
        if ($NoOutput.IsPresent) { $script:SilentMode = $false }
        # Write-Debug "Test-ArcMachinePrivateLinkScopeEnabled: Private Link Scope check completed"
    } # End of end block

} # End Function Test-ArcMachinePrivateLinkScopeEnabled