PoshServerLess.psm1





class AzFunctionsApp {

    [string] $FunctionAppName
    [string] $FunctionAppPath
    [string] $RessourceGroup
    [string] $FunctionHostName
    [string] $FunctionAppStorageName
    [string] $FunctionAppStorageShareName
    [string] $FunctionAppLocation
    
    hidden [Boolean] $FunctionAppExistLocaly = $false
    [hashtable] $functionAppExtension = @{}
    [hashtable] $FunctionAppSettings = @{}
    [AzFunction[]] $Azfunctions = @()

    AzFunctionsApp ([string] $FunctionAppName, [string] $functionAppPath) {
        $this.init($FunctionAppName, $functionAppPath)
    }

    AzFunctionsApp ([string] $FunctionAppName, [string] $functionAppPath, [string] $RessourceGroup) {
        $this.init($FunctionAppName, $functionAppPath, $RessourceGroup)
    }


    AzFunctionsApp ([string] $FunctionAppName, [string] $functionAppPath, [string] $RessourceGroup, [string] $AzureLocation) {
        $this.init($FunctionAppName, $functionAppPath, $RessourceGroup, $AzureLocation)
    }

    hidden init([string] $FunctionAppName, [string] $functionAppPath) {
        $this.FunctionAppName = $FunctionAppName
        $this.FunctionAppPath = $functionAppPath
        $this.ListFunction()      
    }

    hidden init([string] $FunctionAppName, [string] $functionAppPath, [string] $FunctionResourceGroup, [string] $FunctionAppLocation) {

        $this.FunctionAppName = $FunctionAppName
        $this.FunctionAppPath = join-path -path  $functionAppPath -ChildPath $this.FunctionAppName
        $this.FunctionAppLocation = $FunctionAppLocation
        $this.RessourceGroup = $FunctionResourceGroup

        if (!(test-path -Path $this.FunctionAppPath  -ErrorAction SilentlyContinue)) {
            try {
                new-item -Path $this.FunctionAppPath -ItemType Directory

                $Functionrequirements = "@{ 'Az' = '1.*'`" }"

                $FunctionHostConfig = @{
                    "version" = "2.0"
                    "functionTimeout" = "00:05:00"
                    "extensionBundle" = @{
                            "id" = "Microsoft.Azure.Functions.ExtensionBundle"
                            "version"= "[1.*, 2.0.0)"
                        }
                     "logging"= @{
                        "logLevel"= @{
                            "default"= "Information"
                             }
                      "fileLoggingMode"= "always"
                        }
                  "managedDependency"= @{
                    "enabled"= $true
                    }
                }
                
                $profileConfig = "if (`$env:MSI_SECRET -and (Get-Module -ListAvailable Az.Accounts)) { Connect-AzAccount -Identity }"

                new-item -Path (join-path $this.FunctionAppPath -ChildPath "profile.ps1") -ItemType File | Set-Content -PassThru -Encoding utf8 -Value $profileConfig
                new-item -Path (join-path $this.FunctionAppPath -ChildPath "requirements.psd1") -ItemType File | Set-Content -PassThru -Encoding utf8 -Value $Functionrequirements.ToString()
                new-item -Path (join-path $this.FunctionAppPath -ChildPath "host.json") -ItemType File | Set-Content -PassThru -Encoding utf8 -Value ($FunctionHostConfig | convertTo-json -Depth 4)

           } 
           catch {
                Write-Error -Message " Exception Type: $($_.Exception.GetType().FullName) $($_.Exception.Message)"
           }           
        }
        else {
            throw "Can not create a function app in $($this.FunctionAppPath)"
        }


    }

    hidden init([string] $FunctionAppName, [string] $functionAppPath, [string] $FunctionResourceGroup) {

        if ($this.TestAzConnection()) {
            try {
                $FunctionAppConfig = Get-AzWebApp -ResourceGroupName $FunctionResourceGroup -Name $FunctionAppName 

                $this.FunctionAppName = $FunctionAppName
                
                $this.FunctionAppPath = join-path -Path $functionAppPath -ChildPath $FunctionAppName
                $this.RessourceGroup = $FunctionResourceGroup

                $this.FunctionAppLocation = $FunctionAppConfig.Location
                $this.FunctionHostName = $FunctionAppConfig.HostNames[0]
                $WorkerRuntime = ($FunctionAppConfig.SiteConfig.AppSettings | where-object name -eq "FUNCTIONS_WORKER_RUNTIME").Value
                $FunctionExtVerion = ($FunctionAppConfig.SiteConfig.AppSettings | where-object name -eq "FUNCTIONS_EXTENSION_VERSION").Value
                
                $this.FunctionAppStorageShareName = ($FunctionAppConfig.SiteConfig.AppSettings | where-object name -eq "WEBSITE_CONTENTSHARE").Value
                $FunctionStorageConfigString = ($FunctionAppConfig.SiteConfig.AppSettings | where-object name -eq "AzureWebJobsStorage").Value
                $FunctionStorageConfigHash = ConvertFrom-StringData -StringData $FunctionStorageConfigString.Replace(";","`r`n")
                
                $this.FunctionAppStorageName = $FunctionStorageConfigHash.AccountName
                $this.FunctionAppSettings = $FunctionStorageConfigHash

                if ($FunctionExtVerion -ne "~2") {
                    throw "Error this module only support Azure functions v2 with PowerShell"
                }

                $storageAccountObject = Get-AzStorageAccount -ResourceGroupName $FunctionResourceGroup -Name $FunctionStorageConfigHash.AccountName

                $StorageFileObject = Get-AzStorageFile -ShareName $this.FunctionAppStorageShareName  -Context $storageAccountObject.Context -Path "/site/wwwroot"  | Get-AzStorageFile
           
                GetFile -CloudFilesObject $StorageFileObject -context $storageAccountObject.Context -AzurePath "/site/wwwroot" -LocalPath $this.FunctionAppPath -AzureStorageShareName $this.FunctionAppStorageShareName
                
                $this.ListFunction()
            }
            catch {
                Write-Error -Message " Exception Type: $($_.Exception.GetType().FullName) $($_.Exception.Message)"
            }
        }
        else {
            throw "Not connected to Azure, use Login-AzAccount first"
        }
    }

    hidden [void] ListFunction () {

        try {
            if (test-path -Path $this.FunctionAppPath -ErrorAction SilentlyContinue) {
                $this.FunctionAppExistLocaly = $true
                $FunctionList = get-childitem -Path $this.FunctionAppPath -Exclude @("microsoft","bin","obj", "modules") -Directory
            
                foreach ($function in $FunctionList) {
                    
                    $functionPath = join-path -Path $this.FunctionAppPath -ChildPath $function.name
                    $this.Azfunctions += [AzFunction]::new( $FunctionPath, $true)
    
                }
    
                if (test-path -Path (join-path -Path $this.FunctionAppPath -ChildPath "extensions.csproj") -ErrorAction SilentlyContinue) {
    
                    [xml] $CsPRojetExtenstionItem = Get-Content -Path (join-path -Path $this.FunctionAppPath -ChildPath "extensions.csproj")
    
                    foreach ($extension in $CsPRojetExtenstionItem.Project.ItemGroup.PackageReference) {
                        
                        $this.functionAppExtension.Add($extension.Include, $extension.Version)
    
                    }
                }
               
            }
        }
        catch {
            Write-Error -Message " Exception Type: $($_.Exception.GetType().FullName) $($_.Exception.Message)"
        }
 
    }



    [void] RemoveFunction ([string] $Functionname) {

        $NewFunctiongArray = @()
        $functionPath = $null

        foreach ($Azfunction in $this.Azfunctions){

            if ($Azfunction.FunctionName -ne $Functionname) {
               
                $NewFunctiongArray += $Azfunction
            }
            else {
                $functionPath = $Azfunction.FunctionPath
            }

        }

        $this.Azfunctions = $NewFunctiongArray

        if ($null -ne $functionPath) {

            try {
                remove-item -path $functionPath -Force -Recurse
            }
            catch {
                Write-Error -Message " Exception Type: $($_.Exception.GetType().FullName) $($_.Exception.Message)"
            }
        }   

    }

    [void] AddFunction ([AzFunction] $FunctionObject) {

        

        if ( (Split-Path -Path $FunctionObject.FunctionPath -Parent) -eq $this.FunctionAppPath) {

            if (!(test-path -Path $FunctionObject.FunctionPath -ErrorAction SilentlyContinue)) {
                $FunctionObject.WriteFunction()
            }

            $this.Azfunctions += $FunctionObject
        }
        else {
            throw "The Path of The function $($FunctionObject.FunctionName) should be the same as the the Function App"
        }


        
    }

    [boolean] FunctionAppCreated () {
        return $false
    }

    [void] deployFunctionApp () {

    }

    [void] getFunctiondeploymentStatus ([String] $DeployementUserName, [String] $DeployementPassword) {

    }

    

    [void] PublishFunctionApp () {

        $FunctionZippedFolderPath = $this.CompressFunction()

        if ($null -ne $FunctionZippedFolderPath) {

            try {
                Publish-AzWebapp -ResourceGroupName $this.RessourceGroup -Name $this.FunctionAppName -ArchivePath $FunctionZippedFolderPath -force
            }
            catch {
                Write-Error -Message " Exception Type: $($_.Exception.GetType().FullName) $($_.Exception.Message)"
            }

        }
        else {
            throw "Zip file $($FunctionZippedFolderPath) not found"
        }
    }

    [string] CompressFunction () {

        try {
            $TmpFuncZipDeployFileName = [System.IO.Path]::GetRandomFileName()
            $TmpFuncZipDeployFileName = $TmpFuncZipDeployFileName.remove($TmpFuncZipDeployFileName.Length - 4) + ".zip"
            
            $TmpFuncZipDeployPath = join-path -Path $ENV:tmp -ChildPath $TmpFuncZipDeployFileName

            

            $excludeFilesAndFolders = @(".git",".vscode","bin","Microsoft",".funcignore",".gitignore")

            $FileToSendArray = @()

            foreach ($file in get-childitem -Path $this.FunctionAppPath) {
                    if ($file.name -notin $excludeFilesAndFolders) {
                        $FileToSendArray += $file.fullname
                    }
            }

            compress-archive -Path $FileToSendArray -DestinationPath $TmpFuncZipDeployPath
            return $TmpFuncZipDeployPath
        }
        catch {
            Write-Error -Message " Exception Type: $($_.Exception.GetType().FullName) $($_.Exception.Message)"
            return $null
        }

        
    }

    

    [boolean] TestAzConnection () {

        try {
            $AzContext = get-azContext 
            if ($null -eq $AzContext) {
                return $false
            }
            else {
                return $true
            }
        }
        catch [System.Management.Automation.CommandNotFoundException] {
            write-error "No AZURE PowerShell module"
            return $false
        }
        catch {
            Write-Error -Message " Exception Type: $($_.Exception.GetType().FullName) $($_.Exception.Message)"
            return $false
        }
         
    }


}


class AzFunction {

    [string] $FunctionName
    [string] $FunctionPath
    [AzFunctionsTrigger] $TriggerBinding
    [AzFunctionsBinding[]] $Binding = @()
    [Boolean] $overwrite
    [Boolean] $FunctionExist = $false
    [string] $JsonFunctionBindings

    hidden Init([string] $FunctionName, [string] $FunctionPath, [Boolean] $overwrite= $false) {

        $this.overwrite = $overwrite
        $this.FunctionName = $FunctionName
        $this.FunctionPath = $FunctionPath

        $this.TestFunctionPath();

        if ($this.FunctionExist) {
            $this.LoadFunction()
        }

       
        
    }

    hidden [void] TestFunctionPath() {       
        $this.FunctionExist=  test-path -Path $this.FunctionPath -ErrorAction SilentlyContinue
    }

    hidden [Boolean] TestHttpOutBinding() {
        $HttpControl = $false
        if ($this.TriggerBinding.TriggerType -eq "http") { 
            foreach ($binding in $this.Binding) {
                if (($binding.BindingDirection -eq "out") -and ($binding.BindingType -eq "http")) {
                    $HttpControl = $true
                }
            }
        } else {
            $HttpControl = $true
        }
        return $HttpControl
    }

    [void] BuildJsonFunction() {

        $FunctionBinding = New-Object System.Collections.ArrayList 

        $FunctionBinding.add($this.TriggerBinding)

        foreach ($binding in $this.Binding) {
            $FunctionBinding.add($binding)
        }

        $this.JsonFunctionBindings = @{"disabled"=$false; "bindings"=$FunctionBinding} | ConvertTo-Json -Depth 5

    }
    
    AzFunction ([string] $FunctionName, [string] $FunctionPath) {

        $this.init($FunctionName, $FunctionPath, $false)

    }

    AzFunction ([string] $FunctionPath, [Boolean] $overwrite) {

         

        $this.init((split-path -Path $FunctionPath -Leaf),  $FunctionPath, $overwrite)

    }

    AzFunction ([string] $FunctionName, [string] $FunctionPath, [Boolean] $overwrite) {

        $this.init( $FunctionName, $FunctionPath, $overwrite)
    }

    [void] AddBinding ([AzFunctionsBinding]$BindingObject) {
       

        $this.Binding += $BindingObject
        
    }

    [void] AddBindings ([AzFunctionsBinding[]]$BindingObjects) {
         foreach ($bindingObject in $BindingObjects) {
             $this.AddBinding($bindingObject)
         }
    }


    [void] AddTriger ([AzFunctionsTrigger]$Triger) {
        $this.TriggerBinding = $Triger
    }

    [Boolean] testAzFunction () {
        
        
        if (($this.Binding.count -ge 1) -and ($this.TriggerBinding.TriggerType -eq "http") ) {
            return $true
        }
        elseif (($this.Binding.count -ge 0) -and ($this.TriggerBinding.TriggerType -ne "http") -and ($null  -ne $this.TriggerBinding) ) {
            return $true
        }
        else {
            return $false
        }

    }

    [boolean] TestAzFuncBinding ([string] $BindingName) {

        $SearchResult = $false 

        foreach ($binding in $this.Binding){

            if ($binding.BindingName -eq $BindingName) {
                $SearchResult = $true 
            }

        }


        return $SearchResult
    }

    [void] RemoveAzFuncBinding ([string] $BindingName) {

        $NewBindingArray = @()

        foreach ($binding in $this.Binding){

            if ($binding.BindingName -ne $BindingName) {
               
                $NewBindingArray += $binding
            }

        }

        $this.Binding = $NewBindingArray

    }

    [void] WriteFunction () {
        
        $FunctionConfigFile = join-path -Path $this.FunctionPath -ChildPath "function.json"
       
        if ($this.testAzFunction() -and $this.TestHttpOutBinding()) {

            if ((test-path -Path $FunctionConfigFile -ErrorAction SilentlyContinue)) {
                try {
                     remove-item -Path $FunctionConfigFile -Force
                } 
                catch {
                     Write-Error -Message " Exception Type: $($_.Exception.GetType().FullName) $($_.Exception.Message)"
                }
     
             }
     
             if (!(test-path -Path $this.FunctionPath  -ErrorAction SilentlyContinue)) {
                 try {
                     new-item -Path $this.FunctionPath -ItemType Directory
                } 
                catch {
                     Write-Error -Message " Exception Type: $($_.Exception.GetType().FullName) $($_.Exception.Message)"
                }           
             }
     
             try {
                 new-item -ItemType File -Path $FunctionConfigFile 
     
                 $this.BuildJsonFunction()
         
                 Set-Content -Value $this.JsonFunctionBindings -Path $FunctionConfigFile -Encoding utf8
             }
             catch {
                 Write-Error -Message " Exception Type: $($_.Exception.GetType().FullName) $($_.Exception.Message)"
             }
        }
        else {  
            throw "You can not have a function without trigger or a http trigger without a http out binding"
        }



    }

    [void] LoadFunction () {
        $FunctionConfigFile = join-path -Path $this.FunctionPath -ChildPath "function.json"
        if ((test-path -Path $FunctionConfigFile -ErrorAction SilentlyContinue)) {

            try {
                $FunctionJsonConfig = Get-Content $FunctionConfigFile -Raw | ConvertFrom-Json

                ForEach ($Binding in $FunctionJsonConfig.bindings) {
                
                    if ($Binding.Type -like "*Trigger") {
    
                        switch ($Binding.Type) {
                            "timerTrigger" { 
                                $this.AddTriger([timerTrigger]::new($Binding.name, $Binding.Schedule))
                                
                             }
                             "queueTrigger" {
                                
                                $this.AddTriger([queueTrigger]::new($Binding.name, $Binding.queueName, $Binding.connection))
                             }
                             "serviceBusTrigger" {
                                $this.AddTriger([serviceBusTrigger]::new($Binding.name, $Binding.queueName, $Binding.connection))
    
                             }
                             "blobTrigger" {
                                $this.AddTriger( [blobTrigger]::new($Binding.name, $Binding.path, $Binding.connection))
                             }
                             "httpTrigger" {
                                $this.AddTriger( [httpTrigger]::new($Binding.name, $Binding.authLevel, $Binding.methods))
                             }
                        }
                    }
                    else {
                        
                        switch ($Binding.Type) { 
                            "http" {
                                $this.AddBinding([http]::new($Binding.name))
                            }
                            "table" {
                                if ($Binding.Direction -eq "in") {
                                    
                                    [tableIn]$TableInBinding = [tableIn]::new($Binding.name, $Binding.tableName, $Binding.connection)
    
                                    if ($null -ne $Binding.partitionKey) {
                                        $TableInBinding.partitionKey = $Binding.partitionKey
                                    }
    
                                    if ($null -ne $Binding.rowKey) {
                                        $TableInBinding.rowKey = $Binding.rowKey
                                    }
    
                                    if ($null -ne $Binding.take) {
                                        $TableInBinding.take = $Binding.take
                                    }
    
                                    if ($null -ne $Binding.filter) {
                                        $TableInBinding.filter = $Binding.filter
                                    }
    
                                    $this.AddBinding($TableInBinding)
                                }
                                else {
                                    $this.AddBinding( [table]::new($Binding.name, $Binding.tableName, $Binding.connection))
                                }
                            }
                            "blob" {
                                
                                $this.AddBinding([blob]::new($Binding.direction, $Binding.name, $Binding.path, $binding.connection))
                                
                            }
                            "queue" {
                                $this.AddBinding([queue]::new($Binding.name, $Binding.queueName , $Binding.connection))
    
                            }
    
                        }
    
                    }
    
                }
    
                
            }
            catch [System.ArgumentException] {
                Write-Error -Message "Error while reading the json file"
            }
            catch {
                Write-Error -Message " Exception Type: $($_.Exception.GetType().FullName) $($_.Exception.Message)"
            }

        }
    }


}



class AzFunctionsTrigger {
    
    [string] $TriggerType
    [string] $TriggerName
    [string] hidden $direction = "in"
}

class queueTrigger : AzFunctionsTrigger {

    [string] $queueName
    [string] $connection 

    queueTrigger ([string] $triggerName, [String] $queueName, [string] $connection) {

        $this.TriggerType = "queueTrigger"
        $this.TriggerName = $triggerName
        $this.connection = $connection
        $this.queueName = $queueName

    }

}

class timerTrigger : AzFunctionsTrigger {

    [string] $Schedule

    timerTrigger ([string] $triggerName, [string] $Schedule) {
        $this.TriggerType = "timerTrigger"
        $this.TriggerName = $triggerName    
        $this.Schedule = $Schedule 
    }
}

class serviceBusTrigger : AzFunctionsTrigger {

    [string] $queueName
    [string] $connection

    serviceBusTrigger ([string] $triggerName, [string] $queueName, [string] $Connection) {
        $this.TriggerType = "serviceBusTrigger"
        $this.TriggerName = $triggerName 
        $this.queueName = $queueName
        $this.connection = $Connection
    }
}

class httpTrigger : AzFunctionsTrigger {

    [string] $authLevel
    [string[]] $methods = @()

    httpTrigger ([string] $triggerName, [string] $authLevel, [string[]] $methods) {
        $this.TriggerType = "httpTrigger"
        $this.TriggerName = $triggerName 
        $this.methods += $methods
        $this.authLevel = $authLevel
    }

}

class blobTrigger : AzFunctionsTrigger {

    [string] $path
    [string] $connection

    blobTrigger ([string] $triggerName, [string] $path, [string] $connection) {
        $this.TriggerType = "blobTrigger"
        $this.TriggerName = $triggerName 
        $this.connection = $connection
        $this.path = $path
    }
}

class AzFunctionsBinding {

    [string] $BindingName
    [string] $BindingDirection
    [string] $BindingType
    

}

class blob : AzFunctionsBinding {
    [String] $path
    [string] $connection

    blob([string]$direction, [string]$name, [String] $Path, [string] $connection){
        $this.BindingDirection = $direction
        $this.BindingName = $name
        $this.path = $Path
        $this.connection = $connection
        $this.BindingType = "blob"
        
    }
    

}

class http : AzFunctionsBinding {

    
    http ([string] $Name) {
        $this.BindingName = $Name
        $this.BindingDirection = "out"
        $this.BindingType = "http"
    }
}

class queue : AzFunctionsBinding {
    [string] $queueName
    [string] $connection

    queue ([string] $Name, [string] $QueuName, [string] $connection) {
        $this.BindingName = $Name
        $this.connection = $Connection
        $this.queueName = $QueuName
        $this.BindingDirection = "out"
        $this.BindingType = "queue"
    }
}


class table : AzFunctionsBinding {

    
    [string] $connection
    [string] $tableName
    
    table([String]$Name, [string] $tableName, [string] $Connection) {
        $this.BindingName = $Name
        $this.connection = $Connection
        $this.tableName = $tableName
        $this.BindingDirection = "out"
        $this.BindingType = "table"
    }
}

class tableIn : AzFunctionsBinding {

    [string] $connection
    [string] $tableName
    [string] $partitionKey
    [string] $rowkey
    [int] $take
    [string] $filter

    tableIn([String]$Name, [string] $tableName, [string] $Connection) {
        $this.BindingName = $Name
        $this.connection = $Connection
        $this.tableName = $tableName
        $this.BindingDirection = "in"
        $this.BindingType = "table"
    }

}



function GetFile (
    [object[]] $CloudFilesObject, 
    [Microsoft.WindowsAzure.Commands.Common.Storage.LazyAzureStorageContext] $context, 
    [string] $AzurePath= "/site/wwwroot", 
    [string] $LocalPath,
    [String] $AzureStorageShareName
    ) { 

    foreach  ($CloudFile in $CloudFilesObject) {

        $fileobject = Get-AzStorageFile -ShareName $AzureStorageShareName -Context $context -Path $AzurePath | Get-AzStorageFile | where-object name -eq $CloudFile.name

        if ($fileobject.GetType().ToString() -eq "Microsoft.Azure.Storage.File.CloudFile") {
            
            $relative = $AzurePath.replace("/site/wwwroot","")
            $relative = $relative.replace("/","\")
            $relative = Join-Path -Path $LocalPath -ChildPath $relative
            $relative = Join-Path -Path $relative -ChildPath $fileobject.Name
                     
            $fileobject | Get-AzStorageFileContent -Destination $relative

        }
        elseif (($fileobject.name -ne "Microsoft") -and ($fileobject.name -ne "bin") ) {
            
            $azPath = $AzurePath + "/" +$fileobject.Name
           
            $FolderPath = join-path -Path $localPath -ChildPath ($azPath.replace("/site/wwwroot/","")).replace("/","\")

            new-item -Path $FolderPath -ItemType Directory | Out-Null

            $fileobject = Get-AzStorageFile -ShareName $AzureStorageShareName -Context $context -Path $azPath | Get-AzStorageFile
            GetFile -CloudFilesObject $fileobject -context $context -AzurePath $azPath -LocalPath $localPath -AzureStorageShareName $AzureStorageShareName 
        }
    }
}
function add-PoshServerlessFunctionBinding 
{

    <#
    .SYNOPSIS
     
    Add a Binding object to an existing azFunction Object
     
    .DESCRIPTION
     
    Add a Binding object to an existing azFunction Object
     
    .PARAMETER FunctionObject
    Specifies the function Object
     
    .PARAMETER BindingObject
    Specifie the Binding Object
 
 
     
    .EXAMPLE
    $MyFunction = new-PoshServerlessFunction -FunctionAppPath "c:\work\functionAppFolder\" -FunctionName "TimerFunction"
    $Biding = new-PoshServerlessFunctionBinding -Direction out -BindingName MyBinding -BindingType queue -connection MyStorage
 
    add-PoshServerlessFunctionBinding -FunctionObject $MyFunction -BindingObject $Biding
 
            
    #>


    
    [CmdletBinding()]
    param(
        [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [AzFunctionsBinding]
        $BindingObject,

        [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [AzFunction]
        $FunctionObject

    )

    $FunctionObject.AddBinding($BindingObject)

}
function add-PoshServerLessFunctionToApp 
{

    <#
    .SYNOPSIS
     
    Add a function object to an existing Function App Object
     
    .DESCRIPTION
     
    Add a function object to an existing Function App Object
     
    .PARAMETER FunctionObject
    Specifies the function Object
     
    .PARAMETER FunctionAppObject
    Specifie the function App Object
   
    .EXAMPLE
    add-PoshServerLessFunctionToApp -FunctionObject $MyNewFunction -FunctionAppObject $MyApp
            
    #>


    [CmdletBinding()]
    param(
        [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [AzFunctionsApp]
        $FunctionAppObject,

        [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [AzFunction]
        $FunctionObject

    )

    $FunctionAppObject.AddFunction($FunctionObject)

}
function get-PoshServerlessFunction 
{
<#
.SYNOPSIS
 
Read a specific Azure Function from a path
 
.DESCRIPTION
 
Read a specific Azure Function from a path
 
 
.PARAMETER Path
Specifies the function path
 
.PARAMETER OverWrite
switch Specifies if the AZFunc Module should recreate the folder
Default $false, in this case the module only rewrite the function.json
 
.OUTPUTS
 
AzFunction object
 
.EXAMPLE
 
get-PoshServerlessFunction -FunctionPath "c:\work\functionAppFolder\TimerFunction"
Load the function TimerFunction from the FunctionAppFolder
 
.EXAMPLE
 
get-PoshServerlessFunction -FunctionPath "c:\work\functionAppFolder\TimerFunction" -OverWrite
Load the function TimerFunction from the FunctionAppFolder and tell the module to overwrite the function folder
 
#>


    [OutputType([AzFunction])]
    [CmdletBinding()]
    param(
        [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ValueFromPipeline = $true)]
        [ValidateScript({Test-Path $_\function.json})]
        [string]
        $FunctionPath,

        [switch]
        $OverWrite 

    )

    return [AzFunction]::new($FunctionPath, $OverWrite)

}
function get-PoshServerlessFunctionApp
{

    [OutputType([AzFunctionsApp])]
    [CmdletBinding()]
    param(
        [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ValueFromPipeline = $true)]
        [ValidateScript({Test-Path $_\host.json})]
        [string]
        $FunctionAppPath,

        [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [string]
        $FunctionAppName

    )

    return [AzFunctionsApp]::new($FunctionAppName, $FunctionAppPath)





}
function get-PoshServerlessFunctionBinding 
{

    <#
    .SYNOPSIS
     
    retreive Binding from a AzFunc Object
     
    .DESCRIPTION
     
    retreive Binding from a AzFunc Object
     
    .PARAMETER FunctionObject
    Specifies the function Object
     
         
    .EXAMPLE
     
    $FunctionObjectVar | get-PoshServerlessFunctionBinding
     
 
            
    #>


    [OutputType([AzFunctionsBinding[]])]
    [CmdletBinding()]
    param(
        [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ValueFromPipeline = $true)]
        [AzFunction]
        $FunctionObject

    )

    return $FunctionObject.Binding
    
}
function get-PoshServerlessFunctionTrigger
{
    
    <#
    .SYNOPSIS
     
    retreive trigger object from a AzFunc Object
     
    .DESCRIPTION
     
    retreive trigger object from a AzFunc Object
     
    .PARAMETER FunctionObject
    Specifies the function Object
     
         
    .EXAMPLE
     
    $FunctionObjectVar | get-PoshServerlessFunctionBinding
     
 
            
    #>


    [OutputType([AzFunctionsTrigger])]
    [CmdletBinding()]
    param(
        [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ValueFromPipeline = $true)]
        [AzFunction]
        $FunctionObject

    )

    return $FunctionObject.TriggerBinding

}
function new-PoshServerlessFunction 
{
    <#
    .SYNOPSIS
     
    Create a new azure function object
     
    .DESCRIPTION
     
    Create a new azure function object
    The FunctionAppPath and the name parameter will build the function path
     
    .PARAMETER FunctionAppPath
    Specifies the function App path
     
    .PARAMETER FunctionName
    Specifie the name of the function.
 
    .PARAMETER OverWrite
    switch Specifies if the AZFunc Module should recreate the function folder if exist
    Default $false, in this case the module only rewrite the function.json
     
    .OUTPUTS
     
    AzFunction object
     
    .EXAMPLE
     
    new-PoshServerlessFunction -FunctionAppPath "c:\work\functionAppFolder\" -FunctionName "TimerFunction"
    create a new azFunction Object
            
    #>

    [OutputType([AzFunction])]
    [CmdletBinding()]
    param(
        [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateScript({Test-Path $_})]
        [string]
        $FunctionAppPath,

        [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [string]
        $FunctionName,

        [switch]
        $OverWrite 
    )

    $functionPath = join-path -Path $FunctionAppPath -ChildPath $FunctionName

    return [AzFunction]::new($FunctionName,$FunctionPath, $OverWrite)

}
function new-PoshServerlessFunctionApp {
    <#
    .SYNOPSIS
     
    Create a new azure function App object and the function app file
     
    .DESCRIPTION
     
    Create a new azure function App object and the function app file
    It doesn't create the function in Azure
     
    .PARAMETER FunctionAppPath
    Specifies the function App local path, this path must not exist
     
    .PARAMETER FunctionAppName
    The host name of the function App. This Name must be globally unique
 
    .PARAMETER FunctionAppLocation
    The Function App desired location
 
    .PARAMETER FunctionAppResourceGroup
    The Function App desired Resource Group
     
    .OUTPUTS
     
    AzFunctionsApp object
     
    .EXAMPLE
     
    new-PoshServerlessFunctionApp -FunctionAppPath "c:\work\functionAppFolder\" -FunctionAppName "MyFunction01" -FunctionAppLocation "WestEurope" -FunctionAppResourceGroup "MyRg"
    create a new azFunction Object
            
    #>

    [OutputType([AzFunctionsApp])]
    [CmdletBinding()]
    param(
        [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [string]
        $FunctionAppPath,

        [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [string]
        $FunctionAppName,

        [string]
        [ValidateSet("UKSouth", "UKWest", "NorthEurope","WestEurope",    "FranceCentral","SouthAfricaNorth","CentralIndia",    "SouthIndia","WestIndia","JapanEast","JapanWest","KoreaCentral", 
        "EastAsia","SoutheastAsia","AustraliaCentral", "AustraliaCentral2",    "AustraliaEast", "AustraliaSoutheast", "BrazilSouth", "CanadaCentral",
        "CanadaEast", "ChinaEast","ChinaEast2",    "ChinaNorth", "GermanyCentral", "GermanyNortheast","WestUS",
        "WestUS2", "CentralUS", "EastUS","EastUS2","NorthCentralUS","SouthCentralUS","WestCentralUS"
        )]
        $FunctionAppLocation,

        [string]
        $FunctionAppResourceGroup
    )

  
        return [AzFunctionsApp]::new($FunctionAppName,$FunctionAppPath, $FunctionAppResourceGroup, $FunctionAppLocation)


}











function new-PoshServerlessFunctionBinding 
{
    
    <#
    .SYNOPSIS
     
    Create an AzfunctionBinding object
     
    .DESCRIPTION
     
    Create an AzfunctionBinding object
    There are two types of Direction In and Out
    and "blob","http","queue", "table"
 
     
    .PARAMETER Direction
    In or Out
    Queue binding accept only Out direction
 
    .PARAMETER BindingName
    In or Out
    Queue binding accept only Out direction
     
         
    .EXAMPLE
     
    $Biding = new-PoshServerlessFunctionBinding -Direction out -BindingName MyBinding -BindingType queue -connection MyStorage
 
            
    #>


    [OutputType([AzFunctionsBinding])]
    [CmdletBinding()]
    param(
        [parameter(Mandatory = $true, ParameterSetName = "blob")]
        [parameter(Mandatory = $true, ParameterSetName = "queue")]
        [parameter(Mandatory = $true, ParameterSetName = "table")]
        [parameter(Mandatory = $true, ParameterSetName = "http")]
        [ValidateSet("in","Out")]
        [string]
        $Direction, 

        [parameter(Mandatory = $true, ParameterSetName = "blob")]
        [parameter(Mandatory = $true, ParameterSetName = "queue")]
        [parameter(Mandatory = $true, ParameterSetName = "table")]
        [parameter(Mandatory = $true, ParameterSetName = "http")]
        [string]
        $BindingName,       

        [parameter(Mandatory = $true, ParameterSetName = "blob")]
        [parameter(Mandatory = $true, ParameterSetName = "queue")]
        [parameter(Mandatory = $true, ParameterSetName = "table")]
        [parameter(Mandatory = $true, ParameterSetName = "http")]
        [ValidateSet("blob","http","queue", "table")]
        [string]
        $BindingType,   


       [parameter(Mandatory = $true, ParameterSetName = "blob")]
       [string]
       $Path,    

       [parameter(Mandatory = $true, ParameterSetName = "blob")]
       [parameter(Mandatory = $true, ParameterSetName = "queue")]
       [parameter(Mandatory = $true, ParameterSetName = "table")]
       [string]
       $connection,    


       [parameter(Mandatory = $true, ParameterSetName = "queue")]
       [string]
       $queueName,    

       [parameter(Mandatory = $true, ParameterSetName = "table")]
       [string]
       $tableName,  

       [parameter(ParameterSetName = "table")]
       [string]
       $partitionKey, 

       [parameter(ParameterSetName = "table")]
       [string]
       $rowkey, 

       [parameter(ParameterSetName = "table")]
       [int]
       $take, 

       [parameter(ParameterSetName = "table")]
       [string]
       $filter 

    )



    switch ($PSCmdlet.ParameterSetName) {
        "blob" {
            return [blob]::new($direction, $BindingName, $Path, $connection)
        }
        "table" {
            if ($direction -eq "in") {
                $tableinObject =  [table]::new($BindingName,  $tableName, $Connection)

                if ($PSBoundParameters.ContainsKey('filter')) {
                    $tableinObject.filter = $filter
                }
                if ($PSBoundParameters.ContainsKey('take')) {
                    $tableinObject.take = $take
                }
                if ($PSBoundParameters.ContainsKey('rowkey')) {
                    $tableinObject.rowkey = $rowkey
                }
                if ($PSBoundParameters.ContainsKey('partitionKey')) {
                    $tableinObject.partitionKey = $partitionKey
                }
                return $tableinObject
            }
            else {
                return [table]::new($BindingName,  $tableName, $Connection)
            }
        }
        "queue" {
            return [queue]::new($BindingName, $QueuName, $connection)
        }
        "http" {
            return [http]::new($BindingName)
        }
    }

}
function new-PoshServerlessFunctionTrigger 
{
    
    <#
    .SYNOPSIS
     
    Create an AzFunctionTrigger
     
    .DESCRIPTION
     
    Create an AzFunctionTrigger
    There are 5 types
    queueTrigger, timerTrigger, serviceBusTrigger, httpTrigger, blobTrigger
     
    .PARAMETER TriggerType
    Kind of trigger "queueTrigger","timerTrigger", "httpTrigger","serviceBusTrigger","blobTrigger"
 
    .PARAMETER TriggerName
    Name of the trigger. The name will be use in the run.ps1 as a parameter
 
    .PARAMETER connection
    The name of AppSetting for the storage configuration
 
    .PARAMETER queueName
    For queue trigger only, Name of the queue in the storage
 
    .PARAMETER ServiceBusqueueName
    For Service Bus trigger only, Name of the queue in the storage
 
    .PARAMETER Schedule
    For timer trigger only, the schedule in a cron format, 0 * 8 * * *
 
    .PARAMETER methods
    For web trigger only, Allowed HTTP verbs @("POST", "GET")
 
    .PARAMETER authLevel
    For web trigger only, authorisation level
    anonymous no API key needed
    function the function App key is needed
    admin the function master key is needed (this key can be also use in scm)
 
    .PARAMETER authLevel
    For blob trigger only, path in the storage account the function will monitor container/{name}
 
 
 
path
     
         
    .EXAMPLE
     
    $TriggerObject = new-PoshServerlessFunctionTrigger -TriggerName QueueTrigger -TriggerType queueTrigger -queueName myQueue -connection MyAzFuncStorage
 
            
    #>



    [OutputType([AzFunctionsTrigger])]
    [CmdletBinding()]
    param(
        [parameter(Mandatory = $true, ParameterSetName = "serviceBusTrigger")]
        [parameter(Mandatory = $true, ParameterSetName = "queueTrigger")]
        [parameter(Mandatory = $true, ParameterSetName = "blobTrigger")]
        [parameter(Mandatory = $true, ParameterSetName = "timerTrigger")]
        [parameter(Mandatory = $true, ParameterSetName = "httpTrigger")]
        [ValidateSet("queueTrigger","timerTrigger", "httpTrigger","serviceBusTrigger","blobTrigger")]
        [string]
        $TriggerType,   

        [parameter(Mandatory = $true, ParameterSetName = "serviceBusTrigger")]
        [parameter(Mandatory = $true, ParameterSetName = "queueTrigger")]
        [parameter(Mandatory = $true, ParameterSetName = "blobTrigger")]
        [parameter(Mandatory = $true, ParameterSetName = "timerTrigger")]
        [parameter(Mandatory = $true, ParameterSetName = "httpTrigger")]
        [string]
        $TriggerName,   

        [parameter(Mandatory = $true, ParameterSetName = "serviceBusTrigger")]
        [parameter(Mandatory = $true, ParameterSetName = "queueTrigger")]
        [parameter(Mandatory = $true, ParameterSetName = "blobTrigger")]
        [string]
        $connection,    

        [parameter(Mandatory = $true, ParameterSetName = "queueTrigger")]
        [string]
        $queueName, 

        [parameter(Mandatory = $true, ParameterSetName = "serviceBusTrigger")]
        [string]
        $ServiceBusqueueName, 

        [parameter(Mandatory = $true, ParameterSetName = "timerTrigger")]
        [string]
        $Schedule, 

        [parameter(Mandatory = $true, ParameterSetName = "httpTrigger")]
        [string[]]
        $methods, 

        [parameter( ParameterSetName = "httpTrigger")]
        [ValidateSet("function","admin")]
        [string]
        $authLevel = "function", 

        [parameter(Mandatory = $true,  ParameterSetName = "blobTrigger")]
        [string]
        $path 
    )

    switch ($PSCmdlet.ParameterSetName) {
        "queueTrigger" {
            return [queueTrigger]::new($triggerName,  $queueName, $connection)
        }
        "blobTrigger" {
            return [blobTrigger]::new($triggerName,  $path, $connection)
        }
        "httpTrigger" {
            return [httpTrigger]::new($triggerName, $authLevel, $methods)
        }
        "timerTrigger" {
            return [timerTrigger]::new($triggerName,  $Schedule)
        }
        "serviceBusTrigger" {
            return [serviceBusTrigger]::new($triggerName,  $ServiceBusqueueName, $connection)
        }

    }



}
function publish-PoshServerLessFunctionApp 
{
    <#
    .SYNOPSIS
     
    Publish the function to Azure
     
    .DESCRIPTION
     
    Publish the function app to Azure
    This action will replace all the functions inside Azure by those in the function app Object
    You need to have a valid ResourceGroup in the object (for example by using sync-PoshServerlessFunctionApp)
     
    .PARAMETER FunctionAppObject
    Specifies the function Object
     
    .EXAMPLE
 
    $myFunctionApp = sync-PoshServerlessFunctionApp -FunctionName MyFunctionApp01 -ResourceGroupName MyRessourceGroup -LocalFunctionPath 'c:\work\Myfunction'
 
    $myFunction = new-PoshServerlessFunction -FunctionAppPath "c:\work\Myfunction\timerfunc" -FunctionName "TimerFunction"
 
    $TriggerObject = new-PoshServerlessFunctionTrigger -TriggerName QueueTrigger -TriggerType queueTrigger -queueName myQueue -connection MyAzFuncStorage
 
    update-PoshServerlessFunctionTrigger -FunctionObject myFunction -TriggerObject $TriggerObject
 
    add-PoshServerLessFunctionToApp -FunctionObject $myFunction -FunctionAppObject $myFunctionApp
 
    publish-PoshServerLessFunctionApp -FunctionAppObject $myFunctionApp
 
            
    #>


    [CmdletBinding(SupportsShouldProcess, ConfirmImpact='Medium')]
    param(
        [parameter(Mandatory = $true, ValueFromPipeline=$true)]
        [ValidateNotNull()]
        [AzFunctionsApp]
        $FunctionAppObject

    )


    if ($PSCmdlet.ShouldProcess($FunctionAppObject.FunctionAppName,"Publish this Function to Azure, it will rewrite the entire App in Azure")) {

        $FunctionAppObject.PublishFunctionApp()

    }


}

function remove-PoshServerlesstionBinding 
{

    
}
function remove-PoshServerlessFunctionBinding {



    [CmdletBinding()]
    param(
        [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ValueFromPipeline = $true)]
        [AzFunction]
        $FunctionObject,

        [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [string]
        $BindingName

    )

    if (test-PoshServerlessFunctionBinding -FunctionObject $FunctionObject -BindingName $BindingName) {
        $FunctionObject.RemoveAzFuncBinding($BindingName)
    }
    else {
        throw "Error: No Binding found"
    }

}
function remove-PoshServerLessFunctionToApp
{
    <#
    .SYNOPSIS
     
    Remove a function object to an existing Function App Object
     
    .DESCRIPTION
     
    Remove a function object to an existing Function App Object
    It also remove the function from the disk
     
    .PARAMETER FunctionName
    Specifies the function Name
     
    .PARAMETER FunctionAppObject
    Specifie the Binding Object
 
 
     
    .EXAMPLE
     
            
    #>


    [CmdletBinding()]
    param(
        [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [AzFunctionsApp]
        $FunctionAppObject,

        [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [string]
        $FunctionName

    )

    $FunctionAppObject.RemoveFunction($FunctionName)


}
function sync-PoshServerlessFunctionApp 
{
    <#
    .SYNOPSIS
     
    Download a function App content to the local workstation
     
    .DESCRIPTION
     
    Download a function App content to the local workstation
    this cmdlet use Azure PowerShell, you need to install it install-module -name AZ -scope CurrentUser
    You need a valid connexion to azure before, run login-azaccount before runing this cmdlet
     
    .PARAMETER FunctionName
    The function name in Azure
    ex: MyPowerShellAzFunction
 
    .PARAMETER ResourceGroupName
    The name of the ressouce group in Azure where the function app is
 
    .PARAMETER LocalFunctionPath
    The local path to download Azure functions files and folder
    The path should be empty
     
 
    
    .EXAMPLE
    sync-PoshServerlessFunctionApp -FunctionName MyFunctionApp01 -ResourceGroupName MyRessourceGroup -LocalFunctionPath 'c:\work\Myfunction'
    #>



    [OutputType([AzFunctionsApp])]
    [CmdletBinding()]
    param(
        [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ValueFromPipeline = $true)]
        [string]
        $FunctionAppName,

        [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [string]
        $ResourceGroupName,

        [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [string]
        $LocalFunctionPath

    )

    if (test-path -Path $LocalFunctionPath -ErrorAction SilentlyContinue) {

        $FunctionPath = join-path -Path $LocalFunctionPath -ChildPath $FunctionAppName

        if (test-path -Path $FunctionPath -ErrorAction SilentlyContinue) {
            throw "The Path of The function $($LocalFunctionPath) is not empty"
        }
        
      
    } else {
        try {
            new-item -Path $LocalFunctionPath -ItemType Directory | out-null
        }
        catch {
            Write-Error -Message " Exception Type: $($_.Exception.GetType().FullName) $($_.Exception.Message)"
        }
    }


    return [AzFunctionsApp]::new($FunctionAppName, $LocalFunctionPath, $ResourceGroupName)

}
function test-PoshServerlessFunctionBinding 
{
    <#
    .SYNOPSIS
    Test if a binding exist in a AzFunc Object
 
    .DESCRIPTION
    Test if a binding exist in a AzFunc Object, by BindingName
    Return a boolean
    True if the Binding exist
    False if the bindind do not exist
 
    .PARAMETER FunctionObject
    The AzFunction Object to test
 
    .PARAMETER BindingName
    The Binding name to test (string)
 
    .OUTPUTS
    Boolean
 
    .EXAMPLE
 
    test-PoshServerlessFunctionBinding -FunctionObject $FunctionObject -BindingName BindingNameToTest
 
    #>

    [OutputType([boolean])]
    [CmdletBinding()]
    param(
        [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ValueFromPipeline = $true)]
        [AzFunction]
        $FunctionObject,

        [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [string]
        $BindingName

    )

    return $FunctionObject.TestAzFuncBinding($BindingName)
}
function update-PoshServerlessFunctionTrigger 
{

    <#
    .SYNOPSIS
     
    Update an AzFunction Object with a trigger Object
     
    .DESCRIPTION
     
    Update an AzFunction Object with a trigger Object
     
    .PARAMETER FunctionObject
    Specifies the function Object
     
    .PARAMETER TriggerObject
    Specifie the Trigger binding Object
 
    
    .EXAMPLE
 
    $myFunction = new-PoshServerlessFunction -FunctionAppPath "c:\work\Myfunction\timerfunc" -FunctionName "TimerFunction"
 
    $TriggerObject = new-PoshServerlessFunctionTrigger -TriggerName QueueTrigger -TriggerType queueTrigger -queueName myQueue -connection MyAzFuncStorage
 
    update-PoshServerlessFunctionTrigger -FunctionObject myFunction -TriggerObject $TriggerObject
 
 
 
            
    #>


    [CmdletBinding()]
    param(
        [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [AzFunctionsTrigger]
        $triggerObject,

        [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [AzFunction]
        $FunctionObject

    )

    $FunctionObject.AddTriger($triggerObject)
    
}
function write-PoshServerlessFunction 
{
    <#
    .SYNOPSIS
    Update the function folder with the azFunctionObject object
     
    .DESCRIPTION
    Update the function folder with the azFunctionObject object
     
    .PARAMETER FunctionObject
    Specifies the function Object
     
    
    .EXAMPLE
    $AzFunctionObject | write-PoshServerlessFunction
            
    #>



    [CmdletBinding()]
    param(


        [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ValueFromPipeline = $true)]
        [AzFunction]
        $FunctionObject

    )

  
        $FunctionObject.WriteFunction()

  

}