Public/Connect-Cloudserver.ps1

function Get-CloudCredential {
    <#
    .SYNOPSIS
    Prompts for a username and password to connect to the cloud server.
     
    .DESCRIPTION
    Prompts for a username and password to connect to the cloud server. Saves as: $credential
     
    .EXAMPLE
    # Get credentials
    Get-CloudCredential
    #>

    $username = Read-Host "Please enter username"
    $password = Read-Host "Please enter password" -AsSecureString
    $global:credential = New-Object System.Management.Automation.PSCredential($username, $password)
    Write-Host "Credentials saved. Connect with:" -ForegroundColor Green
    Write-Host 'Connect-CloudServer -Server <Hostname/IP> -Credential $credential -IgnoreInvalidCertificate' -ForegroundColor Green
}

function New-SecureCloudCredential {
    <#
    .SYNOPSIS
    Prompts for credentials and securely saves them to an encrypted XML file.
     
    .DESCRIPTION
    Prompts for username and password, then saves them securely using Export-Clixml.
    The credentials are encrypted using Windows Data Protection API (DPAPI) and can only
    be decrypted by the same user on the same computer.
     
    .PARAMETER Path
    The path where the credential file will be saved.
    Default: $env:USERPROFILE\.hypershell\credential.xml
     
    .PARAMETER Force
    Overwrite existing credential file without prompting.
     
    .EXAMPLE
    # Prompts for credentials and saves to default location.
    New-SecureCloudCredential
     
    .EXAMPLE
    # Saves credentials to a custom location. Must use both -UseSavedCredential and -CredentialPath flags when connecting
    New-SecureCloudCredential -Path "C:\MyCredentials\cloud.xml"
    #>

    [CmdletBinding()]
    param(
        #[Parameter(Mandatory = $false)]
        #[string]$Path = "$env:USERPROFILE\.hypershell\credential.xml",
        
        [Parameter(Mandatory = $false)]
        [switch]$Force
    )
    
    process {
        try {
            # Check if file already exists
            if ((Test-Path $Path) -and -not $Force) {
                $overwrite = Read-Host "Credential file already exists at '$Path'. Overwrite? (yes/no)"
                if ($overwrite -ne 'yes') {
                    Write-Host "Operation cancelled." -ForegroundColor Yellow
                    return
                }
            }
            
            # Create directory if it doesn't exist
            $directory = Split-Path -Path $Path -Parent
            if (-not (Test-Path $directory)) {
                New-Item -Path $directory -ItemType Directory -Force | Out-Null
                Write-Verbose "Created directory: $directory"
            }
            
            # Prompt for credentials
            Write-Host "Enter credentials to save securely:" -ForegroundColor Cyan
            $username = Read-Host "Username"
            $password = Read-Host "Password" -AsSecureString
            
            $credential = New-Object System.Management.Automation.PSCredential($username, $password)
            
            # Save credentials securely using Export-Clixml (DPAPI encryption)
            $credential | Export-Clixml -Path $Path -Force
            
            Write-Host "`nCredentials saved securely to: $Path" -ForegroundColor Green
            Write-Host "These credentials can only be decrypted by your user account on this computer." -ForegroundColor Gray
            Write-Host "`nTo use these credentials, run:" -ForegroundColor Yellow
            Write-Host " Connect-CloudServer -Server <hostname> -UseSavedCredential" -ForegroundColor White
        }
        catch {
            Write-Error "Failed to save credentials: $_"
            throw
        }
    }
}

function Get-SecureCloudCredential {
    <#
    .SYNOPSIS
    Retrieves securely saved credentials from an encrypted XML file.
     
    .DESCRIPTION
    Loads credentials that were saved using New-SecureCloudCredential.
    The credentials are decrypted using Windows Data Protection API (DPAPI).
     
    .PARAMETER Path
    The path where the credential file is saved.
    Default: $env:USERPROFILE\.hypershell\credential.xml
     
    .EXAMPLE
    # Get saved credentials from default location.
    $cred = Get-SecureCloudCredential
     
    .EXAMPLE
    # Get credentials from a custom location.
    $cred = Get-SecureCloudCredential -Path "C:\MyCredentials\cloud.xml"
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $false)]
        [string]$Path = "$env:USERPROFILE\.hypershell\credential.xml"
    )
    
    process {
        try {
            if (-not (Test-Path $Path)) {
                throw "Credential file not found at: $Path. Run New-SecureCloudCredential first."
            }
            
            # Load credentials using Import-Clixml (DPAPI decryption)
            $credential = Import-Clixml -Path $Path
            
            Write-Verbose "Loaded credentials for user: $($credential.UserName)"
            return $credential
        }
        catch {
            Write-Error "Failed to load credentials: $_"
            throw
        }
    }
}

function Remove-SecureCloudCredential {
    <#
    .SYNOPSIS
    Deletes the securely saved credential file.
     
    .DESCRIPTION
    Removes the encrypted credential XML file from disk.
     
    .PARAMETER Path
    The path where the credential file is saved.
    Default: $env:USERPROFILE\.hypershell\credential.xml
     
    .PARAMETER Force
    Skip confirmation prompt.
     
    .EXAMPLE
    # Removes saved credentials from default location, prompts for confirmation
    Remove-SecureCloudCredential
     
    .EXAMPLE
    # Removes saved credentials from custom location, bypassing confirmation prompt
    Remove-SecureCloudCredential -Path C:\temp\creds.xml -Force
     
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory = $false)]
        [string]$Path = "$env:USERPROFILE\.hypershell\credential.xml",
        
        [Parameter(Mandatory = $false)]
        [switch]$Force
    )
    
    process {
        try {
            if (-not (Test-Path $Path)) {
                Write-Warning "Credential file not found at: $Path"
                return
            }
            
            if ($Force -or $PSCmdlet.ShouldProcess($Path, "Delete saved credentials")) {
                Remove-Item -Path $Path -Force
                Write-Host "Saved credentials deleted from: $Path" -ForegroundColor Green
            }
        }
        catch {
            Write-Error "Failed to delete credentials: $_"
            throw
        }
    }
}

function Connect-CloudServer {
    <#
    .SYNOPSIS
    Connects to a Cloud Server and establishes an authenticated session.
     
    .DESCRIPTION
    Establishes a connection to a Cloud Server using Basic Authentication and retrieves an API token
    for subsequent API calls. The function supports multiple credential sources with automatic fallback:
     
    1. Explicitly provided -Credential parameter
    2. Saved credentials loaded with -UseSavedCredential switch
    3. Existing $global:credential variable in the session
    4. Auto-loading from saved credential file (if exists)
    5. Interactive prompt (with option to save credentials for future use)
     
    The connection information (token, session, headers) is stored in a module-scoped variable
    $script:CloudConnection for use by other module functions.
     
    Credentials are authenticated via Basic Auth over HTTPS, and a session token is returned
    for use in subsequent API requests.
     
    .PARAMETER Server
    The hostname or IP address of the Cloud Server to connect to.
    This parameter is mandatory.
     
    Examples: "cloud.example.com", "172.23.254.10", "10.0.1.100"
     
    .PARAMETER Credential
    A PSCredential object containing the username and password for authentication.
     
    If not provided, the function will attempt to load credentials from other sources
    (saved credentials, global $credential variable) or prompt the user.
     
    You can create a credential object using Get-Credential or New-Object PSCredential.
     
    .PARAMETER UseSavedCredential
    Switch parameter that explicitly loads credentials from a saved encrypted XML file.
     
    When specified, credentials are loaded from the path specified in -CredentialPath
    (default: $env:USERPROFILE\.hypershell\credential.xml).
     
    Use New-SecureCloudCredential to save credentials securely.
     
    .PARAMETER CredentialPath
    The file path to the saved credential XML file.
     
    Default: "$env:USERPROFILE\.hypershell\credential.xml"
     
    This parameter is only used when -UseSavedCredential is specified or when
    auto-loading saved credentials.
     
    .PARAMETER Port
    The HTTPS port number for the Cloud Server API.
     
    Default: 443
     
    Only change this if your Cloud Server is configured to use a non-standard port.
     
    .PARAMETER IgnoreInvalidCertificate
    Switch parameter that bypasses SSL certificate validation.
     
    Use this when connecting to servers with self-signed certificates or certificates
    that cannot be validated by the local certificate store.
     
    WARNING: Only use this in development/testing environments or when you trust the server.
    Bypassing certificate validation reduces security.
     
    - PowerShell 5.1: Uses a custom certificate policy
    - PowerShell 6+: Uses -SkipCertificateCheck parameter
     
    .EXAMPLE
    #Connects to the server at 172.23.254.10 on port 443, ignoring certificate validation.
    #If no credentials are available, prompts the user for username and password.
    #Offers to save credentials for future use.
     
    Connect-CloudServer -Server "172.23.254.10" -IgnoreInvalidCertificate
     
    .EXAMPLE
    # Connects using credentials previously saved with New-SecureCloudCredential.
    # Loads credentials from the default location: $env:USERPROFILE\.hypershell\credential.xml
     
    Connect-CloudServer -Server "cloud.example.com" -UseSavedCredential
     
    .EXAMPLE
    # Connects to the server on port 8443 using explicitly provided credentials.
    # Prompts for password only (username is pre-filled).
     
    $cred = Get-Credential -UserName "admin"
    Connect-CloudServer -Server "172.23.254.10" -Credential $cred -Port 8443
    
     
    .EXAMPLE
    # First, save credentials for automatic use
    # Demonstrates the automatic credential loading feature.
    # Then connect without specifying credentials (auto-loads saved credentials). If a saved credential file exists, it will be loaded automatically without needing -UseSavedCredential.
     
    New-SecureCloudCredential
    Connect-CloudServer -Server "172.23.254.10" -IgnoreInvalidCertificate
     
    .EXAMPLE
    # Use credentials from a custom location
    # Saves and loads credentials from a custom path instead of the default location.
    # Useful for managing multiple sets of credentials for different environments.
    New-SecureCloudCredential -Path "D:\Credentials\production.xml"
    Connect-CloudServer -Server "prod.cloud.com" -UseSavedCredential -CredentialPath "D:\Credentials\production.xml"
 
     
    .EXAMPLE
    # Use a global credential variable created earlier in the session
    # If $global:credential exists in the session, it will be used automatically.
    # This maintains backward compatibility with scripts using Get-CloudCredential.
    $global:credential = Get-Credential
    Connect-CloudServer -Server "172.23.254.10"
    
     
    .EXAMPLE
    # Connects with verbose output showing credential source, connection steps, and authentication process.
    # Useful for troubleshooting connection issues.
    Connect-CloudServer -Server "dev.cloud.local" -IgnoreInvalidCertificate -Verbose
     
    .NOTES
    File Name : Connect-CloudServer.ps1
    Prerequisite : PowerShell 5.1 or later
     
    The function stores connection information in $script:CloudConnection which includes:
    - Server: The server hostname/IP
    - Port: The connection port
    - BaseUri: The full base URI (https://server:port)
    - Token: The authentication token
    - WebSession: The web session object
    - Headers: Default headers including authentication
    - Connected: Connection status (always $true when function succeeds)
    - ConnectedAt: Timestamp of connection
    - CredentialUsername: Username for re-authentication
    - CredentialSecurePassword: SecureString password for re-authentication
    - IgnoreInvalidCertificate: Certificate validation setting
     
    .LINK
    New-SecureCloudCredential
    Get-SecureCloudCredential
    Remove-SecureCloudCredential
    Disconnect-CloudServer
     
    .OUTPUTS
    None. This function does not return output, but sets the $script:CloudConnection variable
    used by other module functions. Success is indicated by a green "Successfully connected" message.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$Server,
        
        [Parameter(Mandatory = $false)]
        [PSCredential]$Credential,
        
        [Parameter(Mandatory = $false)]
        [switch]$UseSavedCredential,
        
        [Parameter(Mandatory = $false)]
        [string]$CredentialPath = "$env:USERPROFILE\.hypershell\credential.xml",
        
        [Parameter(Mandatory = $false)]
        [int]$Port = 443,
        
        [Parameter(Mandatory = $false)]
        [switch]$IgnoreInvalidCertificate
    )

    begin {
        # Build the base URI
        $baseUri = "https://${Server}:${Port}"
        $loginUri = "${baseUri}/manifold-api/login"

        # Handle certificate validation for PowerShell 5.1
        if ($IgnoreInvalidCertificate -and $PSVersionTable.PSVersion.Major -le 5) {
            if (-not ([System.Management.Automation.PSTypeName]'TrustAllCertsPolicy').Type) {
                Add-Type @"
using System.Net;
using System.Security.Cryptography.X509Certificates;
public class TrustAllCertsPolicy : ICertificatePolicy {
    public bool CheckValidationResult(
        ServicePoint srvPoint, X509Certificate certificate,
        WebRequest request, int certificateProblem) {
        return true;
    }
}
"@

            }
            [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
            [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
        }
    }
    
    process {
        try {
            # Apply certificate policy for PowerShell 5.1 if needed
            if ($IgnoreInvalidCertificate -and $PSVersionTable.PSVersion.Major -le 5) {
                Write-Verbose "Bypassing certificate validation for PowerShell 5.1"
                [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
                [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
            }
            
            # Determine which credentials to use (priority order)
            if (-not $Credential) {
                if ($UseSavedCredential) {
                    # Option 1: Load from saved credential file
                    Write-Verbose "Loading saved credentials from: $CredentialPath"
                    $Credential = Get-SecureCloudCredential -Path $CredentialPath
                    Write-Verbose "Using saved credential for user: $($Credential.UserName)"
                }
                else {
                    # Option 2: Try to use $credential from global scope
                    $Credential = Get-Variable -Name 'credential' -ValueOnly -Scope Global -ErrorAction SilentlyContinue
                    
                    if ($Credential) {
                        Write-Verbose "Using existing global credential for user: $($Credential.UserName)"
                    }
                    else {
                        # Option 3: Check if saved credential file exists (auto-load)
                        if (Test-Path $CredentialPath) {
                            Write-Verbose "Found saved credential file, loading automatically..."
                            $Credential = Get-SecureCloudCredential -Path $CredentialPath
                            Write-Verbose "Using saved credential for user: $($Credential.UserName)"
                        }
                        else {
                            # Option 4: No credentials found anywhere, prompt for them
                            Write-Verbose "No credentials found, prompting user..."
                            Write-Host "Please enter credentials for $Server" -ForegroundColor Yellow
                            
                            $username = Read-Host "Username"
                            $password = Read-Host "Password" -AsSecureString
                            $Credential = New-Object System.Management.Automation.PSCredential($username, $password)
                            
                            # Ask if they want to save for future use
                            $save = Read-Host "Save these credentials securely for future use? (yes/no)"
                            if ($save -eq 'yes') {
                                $directory = Split-Path -Path $CredentialPath -Parent
                                if (-not (Test-Path $directory)) {
                                    New-Item -Path $directory -ItemType Directory -Force | Out-Null
                                }
                                
                                $Credential | Export-Clixml -Path $CredentialPath -Force
                                Write-Host "Credentials saved to: $CredentialPath" -ForegroundColor Green
                            }
                        }
                    }
                }
            }
            else {
                Write-Verbose "Using provided credentials for user: $($Credential.UserName)"
            }
            
            # Create Basic Auth header
            $username = $Credential.UserName
            $password = $Credential.GetNetworkCredential().Password
            $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("${username}:${password}"))
            
            $headers = @{
                "Authorization" = "Basic $base64AuthInfo"
            }
            
            # Make the login request (GET method)
            Write-Verbose "Connecting to $loginUri"
            
            # Build parameters for Invoke-WebRequest
            $restParams = @{
                Uri = $loginUri
                Method = 'Get'
                Headers = $headers
                SessionVariable = 'websession'
            }
            
            # Add SkipCertificateCheck for PowerShell 6+ (Core/7+)
            if ($IgnoreInvalidCertificate -and $PSVersionTable.PSVersion.Major -ge 6) {
                $restParams['SkipCertificateCheck'] = $true
            }
            
            $loginResponse = Invoke-WebRequest @restParams -UseBasicParsing
            $authToken = ($loginResponse.Content | ConvertFrom-Json).token
            
            # Store connection info at module scope INCLUDING credential and certificate flag
            $script:CloudConnection = [PSCustomObject]@{
                Server = $Server
                Port = $Port
                BaseUri = $baseUri
                Token = $authToken
                WebSession = $websession
                Headers = @{
                    "Authorization" = "token $authToken"
                    "Content-Type" = "application/json"
                }
                Connected = $true
                ConnectedAt = Get-Date
                CredentialUsername = $Credential.UserName
                CredentialSecurePassword = $Credential.Password
                IgnoreInvalidCertificate = $IgnoreInvalidCertificate.IsPresent
            }
            
            Write-Host "Successfully connected to $Server" -ForegroundColor Green
        }
        catch {
            Write-Error "Failed to connect to ${Server}: $_"
            $script:CloudConnection = $null
            throw
        }
    }
}

function Disconnect-CloudServer {
    <#
    .SYNOPSIS
    Disconnects from the Cloud Server and clears sensitive data.
     
    .DESCRIPTION
    Clears the connection object, auth token, and session data from memory.
     
    .EXAMPLE
    Disconnect-CloudServer
    #>

    [CmdletBinding()]
    param()
    
    process {
        try {
            if ($script:CloudConnection -and $script:CloudConnection.Connected) {
                Write-Verbose "Disconnecting from $($script:CloudConnection.Server)"
                
                # Clear sensitive data from the connection object
                if ($script:CloudConnection.Token) {
                    $script:CloudConnection.Token = $null
                }
                if ($script:CloudConnection.Headers) {
                    $script:CloudConnection.Headers.Clear()
                    $script:CloudConnection.Headers = $null
                }
                if ($script:CloudConnection.WebSession) {
                    $script:CloudConnection.WebSession = $null
                }
                
                # Clear the entire connection object
                $script:CloudConnection = $null
                
                # Force garbage collection to clear sensitive data from memory
                [System.GC]::Collect()
                [System.GC]::WaitForPendingFinalizers()
                
                Write-Host "Successfully disconnected from Cloud Server" -ForegroundColor Green
            }
            else {
                Write-Warning "No active Cloud Server connection found"
            }
        }
        catch {
            Write-Error "Error during disconnect: $_"
        }
    }
}