<#
.SYNOPSIS
   A CloudStack/CloudPlatform API client.
.DESCRIPTION
   A feature-rich Apache CloudStack/Citrix CloudPlatform API client for issuing commands to the Cloud Management system.
.PARAMETER command
   The command parameter is MANDATORY and specifies which command you are wanting to run against the API.
.PARAMETER options
   Optional command options that can be passed in to commands.

#>
# Writen by Jeff Moody (fifthecho@gmail.com)
# Based off code written by Takashi Kanai (anikundesu@gmail.com)
#
# 2011/9/16  v1.0 created
# 2013/5/13  v1.1 created to work with CloudPlatform 3.0.6 and migrated to entirely new codebase for maintainability and readability.
# 2013/5/17  v2.0 created to modularize everything.
# 2013/6/20  v2.1 created to add Powershell 2 support
# 2013/9/03  v2.5 created to add better error handling
# 2016/8/28  v2.6 Small refactor to fix signature mismatch in some cases. Powershell V3+ only supported (loic.lambiel@exoscale.ch)
# 2018/3/28  v2.7 Switch from XML to JSON output. (yoan.blanc@exoscale.ch)

[VOID][System.Reflection.Assembly]::Load("System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
$WebClient = New-Object net.WebClient

#$DebugPreference = "Continue"

function New-CloudStack{
    Param(
            [Parameter(Mandatory = $true)]
            [String] $apiEndpoint
        ,
            [Parameter(Mandatory = $true)]
            [String] $apiPublicKey
        ,
            [Parameter(Mandatory = $true)]
            [String] $apiSecretKey
        )
    $cloudStack = @()
    $cloudStack += $apiEndpoint
    $cloudStack += $apiPublicKey
    $cloudStack += $apiSecretKey
    return $cloudStack
}
Export-ModuleMember -Function New-CloudStack

function calculateSignature{
    Param(
        [Parameter(Mandatory=$true)]
        [String[]]
        $SECRET_KEY
    ,
        [Parameter(Mandatory=$true)]
        [String]
        $HASH_STRING
    )
    Write-Debug("Hash String:  $HASH_STRING")
    $HMAC_SHA1 = New-Object System.Security.Cryptography.HMACSHA1
    $HMAC_SHA1.key = [Text.Encoding]::ASCII.GetBytes($SECRET_KEY)
    $Digest = $HMAC_SHA1.ComputeHash([Text.Encoding]::ASCII.GetBytes($HASH_STRING))
    $Base64Digest = [System.Convert]::ToBase64String($Digest)  
    $signature = [System.Web.HttpUtility]::UrlEncode($Base64Digest)
    
    Write-Debug("Digest:       $Base64Digest")
    Write-Debug("Signature:    $signature")
    return $signature
}


function Get-CloudStack{
    Param(
        [Parameter(Mandatory=$true)]
        [String[]]
        $cloudStack
    ,
        [Parameter(Mandatory=$true)]
        [String]
        $command
    ,
        [String[]]
        $options
    )
    
    $ADDRESS = $cloudStack[0]
    Write-Debug ("Address: $ADDRESS")
    $API_KEY = $cloudStack[1]
    Write-Debug ("API_KEY: $API_KEY")
    $SECRET_KEY = $cloudStack[2]
    Write-Debug ("SECRET_KEY: $SECRET_KEY")
    Write-Debug ("Options: $options")
    $optionString="apikey="+($API_KEY)
    $options += "command="+$command
    $options += "response=json"
    $options = $options | Sort-Object
    Write-Debug ("Options sorted: $options")
    foreach($o in $options){
        $optionString += "&" + $o.split('=')[0] + "=" + ([System.Web.HttpUtility]::UrlEncode($o.split('=')[1]))
    }
    
    $optionString = $optionString -replace [Regex]::Escape("+"), "%20"

    $URL = $ADDRESS + "?" + $optionString

    Write-Debug("Pre-signed URL: $URL")
    Write-Debug("Option String: $optionString")
    $signature = calculateSignature -SECRET_KEY $SECRET_KEY -HASH_STRING $optionString.ToLower()
    $URL += "&signature="+$signature
    Write-Debug("URL: $URL")
    $Response = ""
    try {
       if ($psversiontable.psversion.Major -ge 3) {
            $Response = Invoke-RestMethod -Uri $URL -Method Get -ErrorAction Stop -ErrorVariable ErrorOut
            Write-Debug $Response
        }
        else {
            Write-Error "PowerShell V3 or later is required"
        }
    }
    catch{
        Write-Error "ERROR!"
        $errorType = $ErrorOut.GetType()
        if ($errorType.Name -eq "ArrayList") {
            Write-Error $ErrorOut[0]
        }
        else {
            Write-Error $ErrorOut
        }
        Write-Error "If this is unclear, please go to $URL in a browser for the API response."
    }
    Write-Debug "Response: $Response"
    return $Response
    
}

Export-ModuleMember -Function Get-CloudStack

function Import-CloudStackConfig{
    # Read configuration values for API Endpoint and keys
    $ChkFile = "$env:userprofile\cloud-settings.txt" 
    $FileExists = (Test-Path $ChkFile -PathType Leaf)

    If (!($FileExists)) 
    {
        Write-Error "Config file does not exist. Writing a basic config that you now need to customize."
        Write-Output "[general]`n" | Out-File $ChkFile
        Write-Output "Address=http://(your URL):8080/client/api`n" | Out-File -Append $ChkFile
        Write-Output "ApiKey=(Your API Key)`n" | Out-File -Append $ChkFile
        Write-Output "SecretKey=(Your Secret Key)" | Out-File -Append $ChkFile
        Return 1
    }
    ElseIf ($FileExists)
    {
        Get-Content "$env:userprofile\cloud-settings.txt" | foreach-object -begin {$h=@{}} -process { $k = [regex]::split($_,'='); if(($k[0].CompareTo("") -ne 0) -and ($k[0].StartsWith("[") -ne $True)) { $h.Add($k[0], $k[1]) } }
        $ADDRESS=$h.Get_Item("Address")
        $API_KEY=$h.Get_Item("ApiKey")
        $SECRET_KEY=$h.Get_Item("SecretKey")
        Write-Debug "Address: $ADDRESS"
        Write-Debug "API Key: $API_KEY"
        Write-Debug "Secret Key: $SECRET_KEY"
        $config = @()
        $config += $ADDRESS
        $config += $API_KEY
        $config += $SECRET_KEY
        if (($ADDRESS -ne "http://(your URL:8080/client/api?") -and ($API_KEY -ne "(Your API Key)") -and ($SECRET_KEY -ne "(Your Secret Key)")) {
            return $config
        }
        else {
            Write-Error "Please configure the $env:userprofile\cloud-settings.txt file"
            return 1
        }
    }
}

Export-ModuleMember -Function Import-CloudstackConfig

function Get-CloudStackUserData{
    Param(
    [Parameter(Mandatory=$true)]
        [String]
        $userdata
    )
    Write-Debug "User Data: $userdata"
    $userdatab64 = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($userdata))
    Write-Debug "Base64 Encoded User Data: $userdatab64"
    $encodeduserdata = [System.Web.HttpUtility]::UrlEncode($userdatab64)
    return $encodeduserdata
}
Export-ModuleMember -Function Get-CloudStackUserData