Logonme.ps1

enum DataType {
    Tenants
    OidcAuthorizations
    OidcTokens
    Saml2Tokens
    AuditEvents
    Users
    Links
    Devices
    Claims
    Roles
    Applications
    HotpTokens
    TotpTokens
    Fido2Tokens
    Passwords
    Templates
    Sessions
}

enum ConfigType {
    CertificatesSigning
    CertificatesEncryption
    CertificatesConnection
    CertificateStoreCurrentUser
    CertificateStoreLocalMachine
    CertificateStoreFile
    CertificateStoreSql
    CredsDirectoryService
    CredsOtp
    CredsPassword
    CredsSharedSecret
    CredsFido2
    DirectoryAd
    DirectoryAdam
    DirectorySql
    DirectoryPortWise
    DirectoryNull
    BankIdV5
    Sts
    StsClaimsMapIngress
    StsClaimsMapEgress
    StsRelyingParty
    Oidc
    OidcClient
    OidcClientSettings
    OidcResource
    OidcScope
    Saml2
    Saml2IdentityProvider
    Saml2ServiceProvider
    AccountSelfService
    AuthenticationPolicy
    AuthenticationPolicyPath
    AuthenticationUserService
    AuthenticationProvisioningService
    AuthenticationEmailChallenge
    FormsMethodBankID
    FormsMethodCapcha
    FormsMethodEmailChallenge
    FormsMethodFido2
    FormsMethodFido2Enrollment
    FormsMethodImpersonation
    FormsMethodOtpEnrollment
    FormsMethodPassword
    FormsMethodPasswordReset
    FormsMethodUserRegistration
    FormsMethodSaml2
    FormsMethodSmsOtp
    FormsMethodSplash
    EmailSmtp
    EmailSendGrid
    SmsTelia
    SmsMobilstart
    SmsEmail
    SmsTurnpike
    UserVerificationBankID
    UserSyncLogonme4
    UserSyncNull
}

class ApiEndpointConfigs {
    [Hashtable] $dataMap_ = @{
        [DataType]::Tenants            = '/data/tenants'
        [DataType]::OidcAuthorizations = '/data/oidc_authorizations'
        [DataType]::OidcTokens         = '/data/oidc_tokens'
        [DataType]::Saml2Tokens        = '/data/saml2_tokens'
        [DataType]::AuditEvents        = '/data/audit_events'
        [DataType]::Users              = '/data/users'
        [DataType]::Devices            = '/data/user_devices'
        [DataType]::Claims             = '/data/user_claims'
        [DataType]::Roles              = '/data/user_roles'
        [DataType]::Applications       = '/data/user_applications'
        [DataType]::Links              = '/data/user_links'
        [DataType]::HotpTokens         = '/data/creds_tokens_hotp'
        [DataType]::TotpTokens         = '/data/creds_tokens_totp'
        [DataType]::Fido2Tokens        = '/data/creds_tokens_fido2'
        [DataType]::Passwords          = '/data/creds_password'
        [DataType]::Templates          = '/data/templates'
        [DataType]::Sessions           = '/data/sessions'
    }

    [Hashtable] $configMap_ = @{
        [ConfigType]::CertificatesSigning          = '/config/certificates_signing'
        [ConfigType]::CertificatesEncryption       = '/config/certificates_encryption'
        [ConfigType]::CertificatesConnection       = '/config/certificates_connection'
        [ConfigType]::CertificateStoreCurrentUser  = '/config/certificates_store_current_user'
        [ConfigType]::CertificateStoreLocalMachine = '/config/certificates_store_local_machine'
        [ConfigType]::CertificateStoreFile         = '/config/certificates_store_file'
        [ConfigType]::CertificateStoreSql          = '/config/certificates_store_sql'
        [ConfigType]::CredsDirectoryService        = '/config/credstore_directory'
        [ConfigType]::CredsOtp                     = '/config/credstore_otp'
        [ConfigType]::CredsPassword                = '/config/credstore_password'
        [ConfigType]::CredsSharedSecret            = '/config/credstore_shared_secret'
        [ConfigType]::CredsFido2                   = '/config/credstore_fido2'
        [ConfigType]::DirectoryAd                  = '/config/directory_service_ad'
        [ConfigType]::DirectoryAdam                = '/config/directory_service_adam'
        [ConfigType]::DirectoryNull                = '/config/directory_service_null'
        [ConfigType]::DirectoryPortWise            = '/config/directory_service_portwise'
        [ConfigType]::DirectorySql                 = '/config/directory_service_sql'
        [ConfigType]::BankIdV5                     = '/config/eid_client_bankidv5'
        [ConfigType]::Sts                          = '/config/sts'
        [ConfigType]::StsClaimsMapIngress          = '/config/sts_claimsmap_ingress'
        [ConfigType]::StsClaimsMapEgress           = '/config/sts_claimsmap_egress'
        [ConfigType]::StsRelyingParty              = '/config/sts_relying_party'
        [Configtype]::Oidc                         = '/config/oidc'
        [ConfigType]::OidcClient                   = '/config/oidc_client'
        [ConfigType]::OidcClientSettings           = '/config/oidc_client_settings'
        [ConfigType]::OidcResource                 = '/config/oidc_resource'
        [ConfigType]::OidcScope                    = '/config/oidc_scope'
        [ConfigType]::Saml2                        = '/config/saml2'
        [ConfigType]::Saml2ServiceProvider         = '/config/saml2_service_provider'
        [ConfigType]::Saml2IdentityProvider        = '/config/saml2_identity_provider'
        [ConfigType]::AccountSelfService           = '/config/provisioning_account_self_service'
        [ConfigType]::AuthenticationPolicy         = '/config/authentication_policy'
        [ConfigType]::AuthenticationPolicyPath     = '/config/authentication_policy_path'
        [ConfigType]::AuthenticationUserService    = '/config/authentication_user_service'
        [ConfigType]::AuthenticationProvisioningService    = '/config/authentication_provisioning_service'
        [ConfigType]::AuthenticationEmailChallenge = '/config/authentication_email_challenge'
        [ConfigType]::FormsMethodBankID            = '/config/forms_bankid'
        [ConfigType]::FormsMethodCapcha            = '/config/forms_capcha'
        [ConfigType]::FormsMethodEmailChallenge    = '/config/forms_email_challenge'
        [ConfigType]::FormsMethodFido2             = '/config/forms_fido2'
        [ConfigType]::FormsMethodFido2Enrollment   = '/config/forms_fido2_enrollment'
        [ConfigType]::FormsMethodImpersonation     = '/config/forms_impersonation'
        [ConfigType]::FormsMethodOtpEnrollment     = '/config/forms_otp_enrollment'
        [ConfigType]::FormsMethodPassword          = '/config/forms_password'
        [ConfigType]::FormsMethodPasswordReset     = '/config/forms_password_reset'
        [ConfigType]::FormsMethodUserRegistration  = '/config/forms_user_registration'
        [ConfigType]::FormsMethodSaml2             = '/config/forms_saml2'
        [ConfigType]::FormsMethodSmsOtp            = '/config/forms_smsotp'
        [ConfigType]::FormsMethodSplash            = '/config/forms_splash'
        [ConfigType]::EmailSmtp                    = '/config/notifier_mail_smtp'
        [ConfigType]::EmailSendGrid                = '/config/notifier_mail_sendgrid'
        [ConfigType]::SmsTelia                     = '/config/notifier_sms_telia'
        [ConfigType]::SmsMobilstart                = '/config/notifier_sms_mobilstart'
        [ConfigType]::SmsEmail                     = '/config/notifier_sms_email'
        [ConfigType]::SmsTurnpike                  = '/config/notifier_sms_turnpike'
        [ConfigType]::UserVerificationBankID       = '/config/user_verification_bankid'
        [ConfigType]::UserSyncLogonme4             = '/config/usersync_logonme4'
        [ConfigType]::UserSyncNull                 = '/config/usersync_null'
    }

    [string] GetDataOdataPath([DataType] $dataType) {
        return $this.dataMap_[$dataType]
    }

    [string] GetConfigOdataPath([ConfigType] $configType) {
        return $this.configMap_[$configType]
    }
}

class ApiCredentials {
    [string] $AccessToken
    [string] $TokenType
    [int] $ExpiresIn;

    ApiCredentials([string]$accessToken, [string]$tokenType, [int]$expiresIn) {
        $this.AccessToken = $accessToken;
        $this.TokenType = $tokenType;
        $this.ExpiresIn = $expiresIn;
    }
}

class ApiConfig {
    [string] $Environment
    [string] $LoginServiceUrl
    [string] $ApiBaseUrl
    [string] $ApiTenantId
    [string] $ApiClientId
    [string] $ApiClientSecret
    [string] $ApiTimeout
    [string] $ApiSetupUsername
    [string] $ApiSetupPassword
    [string] $ServerRoot

    ApiConfig([string]$filePath, [string]$environment) {
        $ini = $this.GetIniContent($filePath)
        $this.Environment = $environment
        $this.LoginServiceUrl = $ini[$environment]["LoginServiceUrl"]
        $this.ApiBaseUrl = $ini[$environment]["ApiBaseUrl"]
        $this.ApiTenantId = $ini[$environment]["ApiTenantId"]
        $this.ApiClientId = $ini[$environment]["ApiClientId"]
        $this.ApiClientSecret = $ini[$environment]["ApiClientSecret"]
        $this.ApiTimeout = $ini[$environment]["ApiTimeout"]
        $this.ApiSetupUsername = $ini[$environment]["ApiSetupUsername"]
        $this.ApiSetupPassword = $ini[$environment]["ApiSetupPassword"]
        $this.ServerRoot = $ini[$environment]["ServerRoot"]
    }

    [HashTable] GetIniContent($filePath) {
        $ini = @{}
        $section = ""
        $CommentCount = 0
        switch -regex -file $filePath {
            "^\[(.+)\]" {
                # Section
                $section = $matches[1]
                $ini[$section] = @{}
                $CommentCount = 0
            }
            "^(;.*)$" {
                # Comment
                $value = $matches[1]
                $CommentCount = $CommentCount + 1
                $name = "Comment" + $CommentCount
                $ini[$section][$name] = $value
            } 
            "(.+?)\s*=(.*)" {
                # Key
                $name, $value = $matches[1..2]
                $ini[$section][$name] = $value
            }
        }
        return $ini
    }

    Dump() {
        Write-Host -Separator "-"
        Write-Host "Environment =" $this.Environment
        Write-Host "LoginServiceUrl =" $this.LoginServiceUrl
        Write-Host "ApiBaseUrl =" $this.ApiBaseUrl
        Write-Host "ApiTenantId =" $this.ApiTenantId
        Write-Host "ApiClientId =" $this.ApiClientId
        Write-Host "ApiClientSecret =" $this.ApiClientSecret
        Write-Host "ApiTimeout =" $this.ApiTimeout
        Write-Host "ApiSetupUsername =" $this.ApiSetupUsername
        Write-Host "ApiSetupPassword =" $this.ApiSetupPassword
        Write-Host -Separator "-"
    }

    DumpEnvironmentVariables() {
        Write-Host -Separator "-"
        Write-Host " LogonmeModuleFile = '$env:LogonmeModuleFile'"
        Write-Host " LogonmeVersion = '$env:LogonmeVersion'"
        Write-Host " LogonmeSharedVolumeDirectory = '$env:LogonmeSharedVolumeDirectory'"
        Write-Host " LogonmeTenant = '$env:LogonmeTenant'"
        Write-Host -Separator "-"
    }
}


if (-not ([System.Management.Automation.PSTypeName]'ServerCertificateValidationCallback').Type) {
    $certCallback = @"
    using System;
    using System.Net;
    using System.Net.Security;
    using System.Security.Cryptography.X509Certificates;
    public class ServerCertificateValidationCallback
    {
        public static void Ignore()
        {
            if(ServicePointManager.ServerCertificateValidationCallback ==null)
            {
                ServicePointManager.ServerCertificateValidationCallback +=
                    delegate
                    (
                        Object obj,
                        X509Certificate certificate,
                        X509Chain chain,
                        SslPolicyErrors errors
                    )
                    {
                        return true;
                    };
            }
        }
    }
"@

    Add-Type $certCallback
}
[ServerCertificateValidationCallback]::Ignore()
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12;

class ApiClient {
    [ApiConfig] $Config
    [ApiEndpointConfigs] $Endpoints
    [ApiCredentials] $Credentials

    ApiClient([ApiConfig] $config) {
        $this.Config = $config;
        $this.Endpoints = [ApiEndpointConfigs]::new();
        $this.Credentials = $this.AquireAccessToken();
    }

    [object] GetByFilter([string] $odataPath, [string] $odataFilter) {
        $uri = ($this.Config.ApiBaseUrl + $odataPath + '/?$filter=' + $odataFilter)
        $accessToken = $this.Credentials.AccessToken
        $headers = @{Authorization = "Bearer $accessToken" }
        $result = Invoke-RestMethod -SkipCertificateCheck -Uri $uri -Method Get -Headers $headers | ForEach-Object { $_ }
        if ($result -isnot [array]) {
            $result = @( $result )
        }
        $result | ForEach-Object {
            $refUri = ($this.Config.ApiBaseUrl + $odataPath + '/' + $_.id)
            $_ | Add-Member -MemberType NoteProperty -Name "URL" -Value $refUri
        }
        return $result
    }

    [object] GetWithBasicAuth([string] $path) {
        $uri = ($this.Config.ApiBaseUrl + $path)
        $accessToken = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}@{1}:{2}" -f $this.Config.ApiSetupUsername, $this.Config.ApiTenantId, $this.Config.ApiSetupPassword)))
        $headers = @{Authorization = "Basic $accessToken" }
        return Invoke-RestMethod -SkipCertificateCheck -Uri $uri -Method Get -Headers $headers -ContentType "application/json"
    }

    [object] Get([string] $odataPath) {
        return $this.Get($odataPath, $null)
    }

    [object] Get([string] $odataPath, [object] $id) {
        $uri = ($this.Config.ApiBaseUrl + $odataPath + '/' + $id)
        $accessToken = $this.Credentials.AccessToken
        $headers = @{Authorization = "Bearer $accessToken" }
        $result = Invoke-RestMethod -SkipCertificateCheck -Uri $uri -Method Get -Headers $headers -ContentType "application/json"
        if ($result -isnot [array]) {
            $result | Add-Member -MemberType NoteProperty -Name "URL"  -Value $uri
            $result = @( $result )
            return $result;
        }
        $result | ForEach-Object {
            $_ | Add-Member -MemberType NoteProperty -Name "URL" -Value ($uri + $_.id)
        }
        return $result
    }

    [object] Post([string] $odataPath, [object] $objData) {
        $uri = ($this.Config.ApiBaseUrl + $odataPath)
        $accessToken = $this.Credentials.AccessToken
        $headers = @{Authorization = "Bearer $accessToken" }
        $obj = $objData.PsObject.Copy()
        $obj.PSObject.Properties.Remove("URL")
        $json = $objData | ConvertTo-Json
        return Invoke-RestMethod -SkipCertificateCheck -Uri $uri -Method Post -Body $json -Headers $headers -ContentType "application/json"
    }

    [object] Upload([string] $odataPath, [object] $id, [string] $uploadFile) {
        $uri = ($this.Config.ApiBaseUrl + $odataPath + '/upload/' + $id)
        $accessToken = $this.Credentials.AccessToken
        $headers = @{Authorization = "Bearer $accessToken" }
        $fileContents = Get-Item $uploadFile
        return Invoke-RestMethod -SkipCertificateCheck -Uri $uri -Method Post -ContentType "multipart/form-data" -Headers $headers -Form @{ file = $fileContents }
    }

    [object] Put([object] $objData) {
        $uri = $objData.URL
        $accessToken = $this.Credentials.AccessToken
        $headers = @{Authorization = "Bearer $accessToken" }
        $obj = $objData.PsObject.Copy()
        $obj.PSObject.Properties.Remove("URL")
        $json = $obj | ConvertTo-Json 
        return Invoke-RestMethod -SkipCertificateCheck -Uri $uri -Method Put -Body $json -Headers $headers -ContentType "application/json"
    }

    [string] Delete([object] $objData) {
        $uri = $objData.URL
        $accessToken = $this.Credentials.AccessToken
        $headers = @{Authorization = "Bearer $accessToken" }
        return Invoke-RestMethod -SkipCertificateCheck -Uri $uri -Method Delete -Headers $headers -ContentType "application/json"
    }

    [ApiCredentials] AquireAccessToken() {
        $Uri = $this.Config.LoginServiceUrl + "/ls/" + $this.Config.ApiTenantId + "/connect/token"
        try {
            $Body = @{
                grant_type    = "client_credentials"
                client_id     = $this.Config.ApiClientId
                client_secret = $this.Config.ApiClientSecret
                scope         = 'management'
                redirect_uri  = 'https://localhost/'
            }
            $res = Invoke-RestMethod -SkipCertificateCheck -Uri $uri -Method Post -Body $body -ContentType "application/x-www-form-urlencoded" 
            return  [ApiCredentials]::new($res.access_token, $res.token_type, $res.expires_in)
        }
        catch {
            $exception = $Error[0]
            Write-Warning "Failed to aquire access token from '$Uri', exception is '$exception'";
            return $null
        }
    }
}


function Get-LogonmeApiClient {
    param(
        [Parameter(
            Mandatory = $false, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [Alias("env")]
        [string] $ConfigEnvironment = 'DEFAULT'
    )
    $configFile = '.\Logonme.ini'
    $configTemplateFile = (Join-Path -Path $PSScriptRoot -ChildPath 'LogonmeTemplate.ini')
    if (-not (Test-Path -Path $configFile)) {
        Write-Warning "Logon.me API Client Configuration file '$configFile' does not exists, copying default template file '$configTemplateFile' to current directory"
        Copy-Item $configTemplateFile $configFile
    }

    $config = [ApiConfig]::new($configFile, $ConfigEnvironment)
    Write-Host "Imported configuration" -ForegroundColor green
    $config.Dump()

    switch ($config.ServerRoot) {
        '.' {
            $dataDirectory = (Get-Location).Path
        } 
        Default {
            $dataDirectory = $config.ServerRoot
        }
    }
    $env:LogonmeTenant = ($config.ApiTenantId)
    $env:LogonmeSharedVolumeDirectory = ($dataDirectory)
    Write-Host "Exported environment variables" -ForegroundColor green
    $config.DumpEnvironmentVariables()
    
    return [ApiClient]::new($config)
}

function Get-LogonmeDockerComposeFile {
    $dockerComposeTemplateFile = (Join-Path -Path $PSScriptRoot -ChildPath 'docker-compose-template.yml') 
    Get-Content -Path $dockerComposeTemplateFile
}


function Get-EnumValues {
    Param([string]$enum)

    $enumValues = @{}
    [enum]::getvalues([type]$enum) |
    ForEach-Object { 
        $enumValues.add($_, $_.value__)
    }
    return $enumValues
}

function Get-LogonmeConfig {
    param(
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [Alias("client")]
        [ApiClient] $ApiClient
        ,
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [Alias("type")]
        [ConfigType]$ConfigType
        ,
        [Parameter(
            Mandatory = $false, 
            ValueFromPipeline = $false,
            ValueFromPipelineByPropertyName = $true)
        ]
        [Alias("filter")]
        [String]$OdataFilter
    )
    $odataPath = $ApiClient.Endpoints.GetConfigOdataPath($ConfigType)
    if ($OdataFilter) {
        $ApiClient.GetByFilter($odataPath, $odataFilter)
    }
    else {
        $ApiClient.Get($odataPath)
    }
}

function Add-LogonmeConfigFromFileData {
    param(
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [Alias("client")]
        [ApiClient] $ApiClient
        ,
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [Alias("type")]
        [ConfigType]$ConfigType
        ,
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [string]$Id
        ,
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [string]$UploadFile
    )
    $odataPath = $ApiClient.Endpoints.GetConfigOdataPath($ConfigType)
    $ApiClient.Upload($odataPath, $id, $uploadFile)
}




function Add-LogonmeConfig {
    param(
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [Alias("client")]
        [ApiClient] $ApiClient
        ,
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [Alias("type")]
        [ConfigType]$ConfigType
        ,
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [string]$Id
    )
    $odataPath = $ApiClient.Endpoints.GetConfigOdataPath($ConfigType)
    $obj = @{ id = $id }
    $temp = $ApiClient.Post($odataPath, $obj)
    $ApiClient.Get($odataPath, $id)
}

function Add-LogonmeData {
    param(
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [Alias("client")]
        [ApiClient] $ApiClient
        ,
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [Alias("type")]
        [DataType]$DataType
        ,
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [object]$obj
    )
    $odataPath = $ApiClient.Endpoints.GetDataOdataPath($DataType)
    return $ApiClient.Post($odataPath, $obj)
}

function Get-LogonmeData {
    param(
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [Alias("client")]
        [ApiClient] $ApiClient
        ,
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [Alias("type")]
        [DataType]$DataType
        ,
        [Parameter(
            Mandatory = $false, 
            ValueFromPipeline = $false,
            ValueFromPipelineByPropertyName = $true)
        ]
        [Alias("filter")]
        [String]$OdataFilter
    )
    $odataPath = $ApiClient.Endpoints.GetDataOdataPath($DataType)
    if ($OdataFilter) {
        $ApiClient.GetByFilter($odataPath, $odataFilter)
    }
    else {
        $ApiClient.Get($odataPath)
    }
}

function Remove-LogonmeObject {
    param (
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [Alias("client")]
        [ApiClient] $ApiClient
        ,
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [Alias("obj")]
        [object]$Object
    )
    $ApiClient.Delete($Object)
}

function Set-LogonmeObject {
    param (
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [Alias("client")]
        [ApiClient] $ApiClient
        ,
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [Alias("obj")]
        [object]$Object
    )
    $ApiClient.Put($Object)
}


Set-Alias -Name Set-LogonmeConfig -Value Set-LogonmeObject
Set-Alias -Name Set-LogonmeData -Value Set-LogonmeObject
Set-Alias -Name Remove-LogonmeConfig -Value Remove-LogonmeObject
Set-Alias -Name Remove-LogonmeData -Value Remove-LogonmeObject


enum SetupOperation {
    SeedConfiguration
    SeedData
    VerifyConfiguration
    VerifyLocalization
    DumpLocalization
    ClearConfigurationCache
    ClearSessionCache
    Drop
}

function Invoke-LogonmeSetup {
    param(  
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [Alias("client")]
        [ApiClient] $ApiClient
        ,
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [SetupOperation]$Operation
        ,
        [Parameter(
            Mandatory = $false, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [Alias("env")]
        [string] $Environment = "develop"
    )

    switch ( $Operation ) {
        SeedConfiguration {
            $ApiClient.GetWithBasicAuth("/setup/seed_configuration" + '?environment=' + $Environment)
        }
        SeedData {
            $ApiClient.GetWithBasicAuth("/setup/seed_data" + '?environment=' + $Environment)
        }
        VerifyConfiguration {
            $ApiClient.GetWithBasicAuth("/setup/verify_configuration")
        }
        VerifyLocalization {
            $uri = ($ApiClient.Config.LoginServiceUrl + '/ls/' + $ApiClient.Config.ApiTenantId + '/localization/verify')
            Invoke-RestMethod -SkipCertificateCheck -Method Get -Uri $uri
        }
        DumpLocalization {
            $uri = ($ApiClient.Config.LoginServiceUrl + '/ls/' + $ApiClient.Config.ApiTenantId + '/localization/i18n')
            Invoke-RestMethod -SkipCertificateCheck -Method Get -Uri $uri
        }
        ClearConfigurationCache {
            $uri = ($ApiClient.Config.LoginServiceUrl + '/ls/' + $ApiClient.Config.ApiTenantId + '/config/clear')
            $res = Invoke-RestMethod -SkipCertificateCheck -Method Get -Uri $uri
            Write-Host $res
        }      
        ClearSessionCache {
            Get-LogonmeData -ApiClient $ApiClient -DataType Sessions | ForEach-Object { Logonme5-DeleteData -client $ApiClient -Object $_ }
        }
        Drop {
            $ApiClient.GetWithBasicAuth("/setup/drop")
        }
    }
}

function Get-LogonmeVersion {
    param(  
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [Alias("client")]
        [ApiClient] $ApiClient
    )

    $managementServiceVersion = $ApiClient.GetWithBasicAuth("/about/version")
    $uri = ($ApiClient.Config.LoginServiceUrl + '/ls/' + $ApiClient.Config.ApiTenantId + '/about/version')
    $loginServiceVersion = Invoke-RestMethod -SkipCertificateCheck -Method Get -Uri $uri

    Write-Host "Management-Service: $managementServiceVersion"
    Write-Host "Login-Service: $loginServiceVersion"
}


function MakeARestCall { 
    param(
        [string] $Uri
        ,
        [string] $AccessToken
        ,
        [string] $Username
        ,
        [string] $LogFile
    )
    try {
        $Headers = @{Authorization = "Bearer $AccessToken" }
        return Invoke-RestMethod -SkipCertificateCheck -Uri $Uri -Method Get -Headers $Headers -ContentType "application/json" -Verbose
    }
    catch {
        Write-Warning "$Username FAILED"
        Add-Content $LogFile $Username
    }
}

function Sync-LogonmeUsers {
    param(  
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [Alias("client")]
        [ApiClient] $ApiClient
        ,
        [Parameter(
            Mandatory = $false, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [Alias("if")]
        [string]$ImportFile
        ,
        [Parameter(
            Mandatory = $false, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [Alias("uid")]
        [string]$Username
    )

    if (-not ($ImportFile -or $Username)) {
        Write-Host "parameter ``ImportFile`` or ``Username`` must be specified"
        return
    }

    $baseUrl = $ApiClient.Config.ApiBaseUrl
    $accessToken = $ApiClient.Credentials.AccessToken
    $funcDef = $function:MakeARestCall.ToString() 

    $logFile = "sync-user-errors.txt"
    if (Test-Path $logFile -PathType leaf) {
        Clear-Content $logFile
    }

    if ($ImportFile) {
        $users = Get-Content $importFile
    }
    if ($Username) {
        $users = @( $Username )
    }


    $users | ForEach-Object -Parallel {
        $function:MakeARestCall = $using:funcDef
        $uri = $using:baseUrl + '/user_provisioning/sync-user?loginName=' + $_
        MakeARestCall -Uri $uri -AccessToken $using:accessToken -Username $_ -LogFile $using:logFile
    } -ThrottleLimit 10 
}

enum AdfsConfigOperation {
    Install
    Uninstall
}

function Get-LogonmeAdfsConfig {
    param(  
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [Alias("client")]
        [ApiClient] $ApiClient
        ,
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [AdfsConfigOperation]$Operation
    )

    switch ( $Operation ) {
        Install {
            $ApiClient.Get('/config/adfs/install')
        }
        Uninstall {
            $ApiClient.Get('/config/adfs/uninstall')
        }
    }
}

function Find-LogonmeUsers {
    param(  
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [Alias("client")]
        [ApiClient] $ApiClient
        ,
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [Alias("l")]
        [string]$Login
    )

    $escLogin = [uri]::EscapeDataString($Login)
    $result = New-Object System.Collections.ArrayList
  

    $u = Get-LogonmeData -ApiClient $ApiClient -DataType Users -OdataFilter "username eq '$escLogin'"
    if ($null -ne $u) {
        $result.Add($u) | Out-Null
    }
    $users = Get-LogonmeData -ApiClient $ApiClient -DataType Users -OdataFilter "emailAddress eq '$escLogin'"
    if ($null -ne $users -and $users.count -gt 0) {
        $result += $users
    }
    $users = Get-LogonmeData -ApiClient $ApiClient -DataType Users -OdataFilter "mobilePhone eq '$escLogin'"
    if ($null -ne $users -and $users.count -gt 0) {
        $result += $users
    }
    return $result
}



function Get-LogonmeUserData {
    param(  
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [Alias("client")]
        [ApiClient] $ApiClient
        ,
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [Alias("l")]
        [string]$Login
        ,
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [DataType]$DataType
    )

    $users = Find-LogonmeUsers -ApiClient $ApiClient -Login $Login
    if ($null -ne $users) {
        if ($users.count -gt 1) {
            Write-Error "User search retured more than multiple users, use 'username' resolve this"
            return
        }
        $u = $users[0]
        $uid = $u.id
    
        return Get-LogonmeData -ApiClient $ApiClient -DataType $DataType -OdataFilter "userId eq '$uid'"
    }
}

function Add-LogonmeUserRole {
    param(  
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [Alias("client")]
        [ApiClient] $ApiClient
        ,
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [Alias("l")]
        [string]$Login
        ,
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [string]$Name
        ,
        [Parameter(
            Mandatory = $false, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [string]$Issuer
        ,
        [Parameter(
            Mandatory = $false, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [string]$ClaimType
    )

    if (-not $Issuer) {
        $Issuer = "Logonme5" # default Issuer
    }

    if (-not $ClaimType) {
        $ClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role" # default claim type
    }

    $users = Find-LogonmeUsers -ApiClient $ApiClient -Login $login
    if ($null -ne $users) {
        if ($users.count -gt 1) {
            Write-Error "User search retured more than multiple users, use 'username' resolve this"
            return
        }
        $u = $users[0]

        $obj = @{
            id        = "rid-" + [guid]::NewGuid().ToString("n")
            userId    = $u.id
            issuer    = $Issuer
            name      = $Name
            claimType = $ClaimType
        }
        $temp = Add-LogonmeData -ApiClient $ApiClient -DataType Roles -obj $obj
        if ($temp) {
            $uid = $obj.userId
            $rid = $obj.id
            Write-Host "Successfully created role '$Name' ($rid) for user '$uid'"
            return Get-LogonmeData -ApiClient $ApiClient -DataType Roles -OdataFilter "id eq '$rid'" 
        }
        Write-Error "Failed to create role '$Name' for user '$u'"

    }
    else {
        Write-Error "User not found"
    }
}

function Export-LogonmeData {
    param(  
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [Alias("client")]
        [ApiClient] $ApiClient
    )

    Get-LogonmeData -ApiClient $ApiClient -DataType Tenants
    Get-LogonmeData -ApiClient $ApiClient -DataType OidcAuthorizations
    Get-LogonmeData -ApiClient $ApiClient -DataType OidcTokens
    Get-LogonmeData -ApiClient $ApiClient -DataType Saml2Tokens
    Get-LogonmeData -ApiClient $ApiClient -DataType AuditEvents
    Get-LogonmeData -ApiClient $ApiClient -DataType Users
    Get-LogonmeData -ApiClient $ApiClient -DataType Devices
    Get-LogonmeData -ApiClient $ApiClient -DataType Claims
    Get-LogonmeData -ApiClient $ApiClient -DataType Roles
    Get-LogonmeData -ApiClient $ApiClient -DataType Applications
    Get-LogonmeData -ApiClient $ApiClient -DataType Links
    Get-LogonmeData -ApiClient $ApiClient -DataType HotpTokens
    Get-LogonmeData -ApiClient $ApiClient -DataType TotpTokens
    Get-LogonmeData -ApiClient $ApiClient -DataType Fido2Tokens
    Get-LogonmeData -ApiClient $ApiClient -DataType Passwords
    Get-LogonmeData -ApiClient $ApiClient -DataType Sessions
}

function Export-LogonmeConfigs {
    param(  
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [Alias("client")]
        [ApiClient] $ApiClient
    )

    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType CertificatesSigning
    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType CertificatesEncryption
    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType CertificatesConnection

    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType CertificateStoreCurrentUser
    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType CertificateStoreLocalMachine
    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType CertificateStoreFile
    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType CertificateStoreSql

    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType CredsDirectoryService
    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType CredsOtp
    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType CredsPassword
    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType CredsSharedSecret

    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType DirectoryAd
    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType DirectoryAdam
    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType DirectorySql
    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType DirectoryPortWise

    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType BankIdV5

    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType Sts
    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType StsClaimsMapIngress
    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType StsClaimsMapEgress
    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType StsRelyingParty

    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType Oidc
    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType OidcClient
    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType OidcClientSettings
    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType OidcResource
    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType OidcScope

    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType Saml2
    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType Saml2ServiceProvider
    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType Saml2IdentityProvider

    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType AccountSelfService

    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType AuthenticationPolicy
    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType AuthenticationPolicyPath
    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType AuthenticationUserService
    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType AuthenticationProvisioningService
    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType AuthenticationEmailChallenge

    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType FormsMethodBankID
    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType FormsMethodCapcha
    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType FormsMethodEmailChallenge
    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType FormsMethodFido2
    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType FormsMethodFido2Enrollment
    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType FormsMethodImpersonation
    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType FormsMethodOtpEnrollment
    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType FormsMethodPassword
    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType FormsMethodPasswordReset
    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType FormsMethodUserRegistration
    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType FormsMethodSmsOtp
    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType FormsMethodSplash
 
    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType EmailSmtp
    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType EmailSendGrid

    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType SmsTelia
    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType SmsMobilstart
    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType SmsEmail
    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType SmsTurnpike

    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType UserVerificationBankID

    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType UserSyncLogonme4
    Get-LogonmeConfig -ApiClient $ApiClient -ConfigType UserSyncNull
}

function New-LogonmeIisWebSite {
    param(  
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [Alias("dir")]
        [string] $deploymentDirectory
        ,
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [Alias("svc")]
        [string] $svcName
        ,
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [Alias("site")]
        [string] $siteName
        ,
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [Alias("port")]
        [int] $svcPort
        ,
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [Alias("cert")]
        [string] $svcServerCertThumbprint
    )

    $physicalPath = Join-Path -Path $deploymentDirectory -ChildPath "Services/$svcName/Bin"
    if (-not (Test-Path $physicalPath)) {
        Write-Error "IIS physical path for service '$svcName' is set to directory '$deplophysicalPathymentDirectory' but this directory does not exists."
        return $false
    }

    New-Item -Path $physicalPath -Name $siteName -ItemType "directory" -Force | Out-Null
    New-WebAppPool -Name $siteName | Out-Null
    New-WebSite -Name $siteName -Port $svcPort -Ssl -SslFlags 0 -PhysicalPath $physicalPath -ApplicationPool $siteName | Out-Null
    $site = Get-ChildItem -Path "IIS:\Sites" | Where-Object {( $_.Name -eq $siteName )}
    $binding = $site.Bindings.Collection | Where-Object {( $_.protocol -eq 'https')}
    $binding.AddSslCertificate($svcServerCertThumbprint, "my")

    Set-ItemProperty -Path "IIS:\AppPools\$siteName" -Name managedRuntimeVersion -Value ""

    return $true
}


function Remove-LogonmeIisWebSite {
    param(  
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [Alias("site")]
        [string] $siteName
    )

    Remove-WebAppPool -Name $siteName
    Remove-WebSite -Name $siteName
}


function Set-LogonmeDeploymentDirectoryPermissions {
    param(  
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [Alias("dir")]
        [string] $deploymentDirectory
        ,
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [Alias("svc")]
        [string] $svcName
    )

    $svcPool = "IIS AppPool\$svcName"

    $aclPath = $deploymentDirectory
    $acl = Get-ACL -Path $aclPath
    $accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule($svcPool, "Read", "ContainerInherit, ObjectInherit", "None", "Allow")
    $acl.SetAccessRule($accessRule)
    Write-Host "Service $svcName setting default inherited 'Read' permissions on '$aclPath' and subfolders"
    $acl | Set-Acl -Path $aclPath

    $aclPath = Join-Path -Path $deploymentDirectory -ChildPath "ServerRoot\Db"
    $acl = Get-ACL -Path $aclPath
    $accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule($svcPool, "Modify", "None", "None", "Allow")
    $acl.SetAccessRule($accessRule)
    Write-Host "Service $svcName setting 'Modify' permissions on '$aclPath'"
    $acl | Set-Acl -Path $aclPath

    $aclPath = Join-Path -Path $deploymentDirectory -ChildPath "ServerRoot\Logs"
    $acl = Get-ACL -Path $aclPath
    $accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule($svcPool, "Modify", "None", "None", "Allow")
    $acl.SetAccessRule($accessRule)
    Write-Host "Service $svcName setting 'Modify' permissions on '$aclPath'"
    $acl | Set-Acl -Path $aclPath

    $aclPath = Join-Path -Path $deploymentDirectory -ChildPath "ServerRoot\Keys"
    $acl = Get-ACL -Path $aclPath
    $accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule($svcPool, "Modify", "None", "None", "Allow")
    $acl.SetAccessRule($accessRule)
    Write-Host "Service $svcName setting 'Modify' permissions on '$aclPath'"
    $acl | Set-Acl -Path $aclPath
}


enum IisSetupOperation {
    Install
    Uninstall
}

function Invoke-LogonmeIisSetup {
    param(  
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [IisSetupOperation]$Operation
        ,
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [Alias("dir")]
        [string] $deploymentDirectory
        ,
        [Parameter(
            Mandatory = $false, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [Alias("cert")]
        [X509Certificate] $serverCertificate
    )

    Import-Module WebAdministration

    if (-not (Test-Path $deploymentDirectory)) {
        Write-Error "Deployment directory '$deploymentDirectory' does not exists, please unpack release tar, e.g. 'tar xvf Logonme.x.y.x.tar' file to create this directory."
        return $false
    }
    Write-Host "Using deployment directory '$deploymentDirectory'."


    switch ( $Operation ) {
        Install {
            if (-not $serverCertificate) {
                Write-Error "Logon.me server certificate '$serverCertificate' not found."
                return $false
            }
            $thumb = $serverCertificate.GetCertHashString()
            Write-Host "Using server certificate with thumbprint '$thumb'"

            New-LogonmeIisWebSite -deploymentDirectory $deploymentDirectory -svcName "LoginService" -siteName "login-service" -svcPort 443 -svcServerCertThumbprint $thumb
            New-LogonmeIisWebSite -deploymentDirectory $deploymentDirectory -svcName "ManagementService" -siteName "management-service" -svcPort 5006 -svcServerCertThumbprint $thumb
            Set-LogonmeDeploymentDirectoryPermissions -deploymentDirectory $deploymentDirectory -svcName "LoginService"
            Set-LogonmeDeploymentDirectoryPermissions -deploymentDirectory $deploymentDirectory -svcName "ManagementService"
        }
        Uninstall {
            Remove-LogonmeIisWebSite "login-service"
            Remove-LogonmeIisWebSite "management-service"
        }
    }
}

function Get-FirstDockerImageFromFile {
    param (
        [Parameter(
            Mandatory = $true)
        ]
        [string]$DockerComposeFile
    )
    $text = Get-Content -Path $DockerComposeFile
    $regex = "(.*?):[\s]{0,2}([^']{0,1}[\%]?[\w\d]+[\%]?[\/,\-\.\(\)\%\\h\p{L}]+)"
    $res = [regex]::Matches($text, $regex);
    $firstImage =  $res[1].Groups[2].Value
    return $firstImage.Trim()
}

function Build-LogonmeDockerImageFromServiceDirectory {
    param (
        [Parameter(
            Mandatory = $true)
        ]
        [string]$ServiceDirectory
        ,
        [Parameter()]
        [Switch]
        $Publish
    )
    Push-Location (Get-Location)

    Set-Location $ServiceDirectory

    $DockerComposeFile = 'docker-compose.yml' 
    $DockerImage = Get-FirstDockerImageFromFile -DockerComposeFile $DockerComposeFile
    $ServiceName = Split-Path -Path $ServiceDirectory -Leaf
    $ServiceExecutable = "Bin\$ServiceName.dll"

    if (-not (Test-Path $ServiceExecutable)) {
        Write-Error "Service executable '$ServiceExecutable' does not exists"
        return $false
    }
    $gitVersion = (Get-Item $ServiceExecutable).VersionInfo.FileVersion
    Write-Host "Retrieved file version '$gitVersion' for '$ServiceExecutable'" -ForegroundColor Cyan 

    Write-Host "Creating docker image content for project service '$DockerImage' in '$ServiceDirectory'" -ForegroundColor Cyan 
    Remove-Item -Force 'logonme.tar' -ErrorAction Ignore | Out-Null
    ($foo = tar cvf logonme.tar Bin) 2> $null

    Write-Host "Composing docker image for service '$DockerImage'"
    docker-compose -f $DockerComposeFile build
    
    Write-Host "Setting docker image tags to: $gitVersion"
    docker tag $DockerImage ${DockerImage}:$gitVersion

    $success = $false
    if ($Publish) {
        Write-Host "Publishing docker image to registry"
        docker push ${DockerImage}:$gitVersion
        if ($LASTEXITCODE -ne 0) {
            $success = $false
        }
    }
    
    Pop-Location
    return $success
}

function Build-LogonmeDockerImages {
    param(  
        [Parameter(
            Mandatory = $true, 
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)
        ]
        [Alias("dir")]
        [string] $deploymentDirectory
    )

    $services = Get-ChildItem (Join-Path $deploymentDirectory -ChildPath "Services") -Directory
    
    foreach ($serviceDirectory in $services) {
        Write-Host "Building docker image in service directory '$serviceDirectory'"
        $success = Build-LogonmeDockerImageFromServiceDirectory -ServiceDirectory $serviceDirectory 
        if (!$success) {
            Write-Error "Failed to build/publish docker image for servcice directory '$serviceDirectory'"
            return
        }
    }

    Write-Host "Successfully built docker images" -ForegroundColor Green
}