diff --git a/AptecoPSFramework/boilerplate/core_broadcast.ps1 b/AptecoPSFramework/boilerplate/core_broadcast.ps1 new file mode 100644 index 0000000..e741a09 --- /dev/null +++ b/AptecoPSFramework/boilerplate/core_broadcast.ps1 @@ -0,0 +1,99 @@ +################################################ +# +# INPUT +# +################################################ + +Param( + [hashtable] $params +) + + +################################################ +# +# NOTES +# +################################################ + + +<# + +This script is used for when you want everything to be executed with PowerShell Core + +This one gets called and uses the original scripts through pwsh + +#> + +################################################ +# +# SETTINGS +# +################################################ + +# temporary files to handle objects +$inputFile = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath("$( $Env:Temp )/$( [System.Guid]::NewGuid().toString() )_input.tmp") +$outputFile = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath("$( $Env:Temp )/$( [System.Guid]::NewGuid().toString() )_output.tmp") + +$verb = "Broadcast" + + +################################################ +# +# PROGRAM +# +################################################ + + +#----------------------------------------------- +# FIND OUT CURRENT DIRECTORY +#----------------------------------------------- + +$settingsFile = Get-Item -Path $params.settingsFile + + +#----------------------------------------------- +# CHECK IF PWSH IS INSTALLED +#----------------------------------------------- + +$isPwshInstalled = $false +try { + if ( (pwsh { 1+1 }) -eq 2 ) { + $isPwshInstalled = $true + } +} catch { + #"not there" +} + + +#----------------------------------------------- +# START PWSH PROCESS +#----------------------------------------------- + +If ( $isPwshInstalled -eq $true ) { + + # Save the hashtable to a json file + $htInput | ConvertTo-Json -Compress -Depth 99 | Set-Content -Path $inputFile -Encoding UTF8 + + # Call pwsh with the test file and some parameters and wait for finish + # TODO [ ] Add error handling and maybe timeout? + $coreWrapper = Join-Path -Path $settingsFile.DirectoryName -ChildPath "core_wrapper.ps1" + pwsh -File $coreWrapper -Verb $verb -InputFile $inputFile -OutputFile $outputFile + + # Read return values + $j = Get-Content -Path $outputFile -Raw -Encoding UTF8 | ConvertFrom-Json + + # Convert the PSCustomObject back to a hashtable + $htOutput = [Hashtable]@{} + $j.psobject.properties | ForEach-Object { + $htOutput[$_.Name] = $_.Value + } + + # Remove the temporary json files + Remove-Item -Path $InputFile + Remove-Item -Path $OutputFile + +} else { + + "Sorry, please install pwsh" + +} \ No newline at end of file diff --git a/AptecoPSFramework/boilerplate/core_getmessagelists.ps1 b/AptecoPSFramework/boilerplate/core_getmessagelists.ps1 new file mode 100644 index 0000000..aa1718a --- /dev/null +++ b/AptecoPSFramework/boilerplate/core_getmessagelists.ps1 @@ -0,0 +1,99 @@ +################################################ +# +# INPUT +# +################################################ + +Param( + [hashtable] $params +) + + +################################################ +# +# NOTES +# +################################################ + + +<# + +This script is used for when you want everything to be executed with PowerShell Core + +This one gets called and uses the original scripts through pwsh + +#> + +################################################ +# +# SETTINGS +# +################################################ + +# temporary files to handle objects +$inputFile = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath("$( $Env:Temp )/$( [System.Guid]::NewGuid().toString() )_input.tmp") +$outputFile = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath("$( $Env:Temp )/$( [System.Guid]::NewGuid().toString() )_output.tmp") + +$verb = "GetMessageLists" + + +################################################ +# +# PROGRAM +# +################################################ + + +#----------------------------------------------- +# FIND OUT CURRENT DIRECTORY +#----------------------------------------------- + +$settingsFile = Get-Item -Path $params.settingsFile + + +#----------------------------------------------- +# CHECK IF PWSH IS INSTALLED +#----------------------------------------------- + +$isPwshInstalled = $false +try { + if ( (pwsh { 1+1 }) -eq 2 ) { + $isPwshInstalled = $true + } +} catch { + #"not there" +} + + +#----------------------------------------------- +# START PWSH PROCESS +#----------------------------------------------- + +If ( $isPwshInstalled -eq $true ) { + + # Save the hashtable to a json file + $htInput | ConvertTo-Json -Compress -Depth 99 | Set-Content -Path $inputFile -Encoding UTF8 + + # Call pwsh with the test file and some parameters and wait for finish + # TODO [ ] Add error handling and maybe timeout? + $coreWrapper = Join-Path -Path $settingsFile.DirectoryName -ChildPath "core_wrapper.ps1" + pwsh -File $coreWrapper -Verb $verb -InputFile $inputFile -OutputFile $outputFile + + # Read return values + $j = Get-Content -Path $outputFile -Raw -Encoding UTF8 | ConvertFrom-Json + + # Convert the PSCustomObject back to a hashtable + $htOutput = [Hashtable]@{} + $j.psobject.properties | ForEach-Object { + $htOutput[$_.Name] = $_.Value + } + + # Remove the temporary json files + Remove-Item -Path $InputFile + Remove-Item -Path $OutputFile + +} else { + + "Sorry, please install pwsh" + +} \ No newline at end of file diff --git a/AptecoPSFramework/boilerplate/core_getmessages.ps1 b/AptecoPSFramework/boilerplate/core_getmessages.ps1 new file mode 100644 index 0000000..220b0e4 --- /dev/null +++ b/AptecoPSFramework/boilerplate/core_getmessages.ps1 @@ -0,0 +1,99 @@ +################################################ +# +# INPUT +# +################################################ + +Param( + [hashtable] $params +) + + +################################################ +# +# NOTES +# +################################################ + + +<# + +This script is used for when you want everything to be executed with PowerShell Core + +This one gets called and uses the original scripts through pwsh + +#> + +################################################ +# +# SETTINGS +# +################################################ + +# temporary files to handle objects +$inputFile = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath("$( $Env:Temp )/$( [System.Guid]::NewGuid().toString() )_input.tmp") +$outputFile = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath("$( $Env:Temp )/$( [System.Guid]::NewGuid().toString() )_output.tmp") + +$verb = "GetMessages" + + +################################################ +# +# PROGRAM +# +################################################ + + +#----------------------------------------------- +# FIND OUT CURRENT DIRECTORY +#----------------------------------------------- + +$settingsFile = Get-Item -Path $params.settingsFile + + +#----------------------------------------------- +# CHECK IF PWSH IS INSTALLED +#----------------------------------------------- + +$isPwshInstalled = $false +try { + if ( (pwsh { 1+1 }) -eq 2 ) { + $isPwshInstalled = $true + } +} catch { + #"not there" +} + + +#----------------------------------------------- +# START PWSH PROCESS +#----------------------------------------------- + +If ( $isPwshInstalled -eq $true ) { + + # Save the hashtable to a json file + $htInput | ConvertTo-Json -Compress -Depth 99 | Set-Content -Path $inputFile -Encoding UTF8 + + # Call pwsh with the test file and some parameters and wait for finish + # TODO [ ] Add error handling and maybe timeout? + $coreWrapper = Join-Path -Path $settingsFile.DirectoryName -ChildPath "core_wrapper.ps1" + pwsh -File $coreWrapper -Verb $verb -InputFile $inputFile -OutputFile $outputFile + + # Read return values + $j = Get-Content -Path $outputFile -Raw -Encoding UTF8 | ConvertFrom-Json + + # Convert the PSCustomObject back to a hashtable + $htOutput = [Hashtable]@{} + $j.psobject.properties | ForEach-Object { + $htOutput[$_.Name] = $_.Value + } + + # Remove the temporary json files + Remove-Item -Path $InputFile + Remove-Item -Path $OutputFile + +} else { + + "Sorry, please install pwsh" + +} \ No newline at end of file diff --git a/AptecoPSFramework/boilerplate/core_preview.ps1 b/AptecoPSFramework/boilerplate/core_preview.ps1 new file mode 100644 index 0000000..d7649eb --- /dev/null +++ b/AptecoPSFramework/boilerplate/core_preview.ps1 @@ -0,0 +1,99 @@ +################################################ +# +# INPUT +# +################################################ + +Param( + [hashtable] $params +) + + +################################################ +# +# NOTES +# +################################################ + + +<# + +This script is used for when you want everything to be executed with PowerShell Core + +This one gets called and uses the original scripts through pwsh + +#> + +################################################ +# +# SETTINGS +# +################################################ + +# temporary files to handle objects +$inputFile = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath("$( $Env:Temp )/$( [System.Guid]::NewGuid().toString() )_input.tmp") +$outputFile = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath("$( $Env:Temp )/$( [System.Guid]::NewGuid().toString() )_output.tmp") + +$verb = "Preview" + + +################################################ +# +# PROGRAM +# +################################################ + + +#----------------------------------------------- +# FIND OUT CURRENT DIRECTORY +#----------------------------------------------- + +$settingsFile = Get-Item -Path $params.settingsFile + + +#----------------------------------------------- +# CHECK IF PWSH IS INSTALLED +#----------------------------------------------- + +$isPwshInstalled = $false +try { + if ( (pwsh { 1+1 }) -eq 2 ) { + $isPwshInstalled = $true + } +} catch { + #"not there" +} + + +#----------------------------------------------- +# START PWSH PROCESS +#----------------------------------------------- + +If ( $isPwshInstalled -eq $true ) { + + # Save the hashtable to a json file + $htInput | ConvertTo-Json -Compress -Depth 99 | Set-Content -Path $inputFile -Encoding UTF8 + + # Call pwsh with the test file and some parameters and wait for finish + # TODO [ ] Add error handling and maybe timeout? + $coreWrapper = Join-Path -Path $settingsFile.DirectoryName -ChildPath "core_wrapper.ps1" + pwsh -File $coreWrapper -Verb $verb -InputFile $inputFile -OutputFile $outputFile + + # Read return values + $j = Get-Content -Path $outputFile -Raw -Encoding UTF8 | ConvertFrom-Json + + # Convert the PSCustomObject back to a hashtable + $htOutput = [Hashtable]@{} + $j.psobject.properties | ForEach-Object { + $htOutput[$_.Name] = $_.Value + } + + # Remove the temporary json files + Remove-Item -Path $InputFile + Remove-Item -Path $OutputFile + +} else { + + "Sorry, please install pwsh" + +} \ No newline at end of file diff --git a/AptecoPSFramework/boilerplate/core_test.ps1 b/AptecoPSFramework/boilerplate/core_test.ps1 new file mode 100644 index 0000000..9b174b1 --- /dev/null +++ b/AptecoPSFramework/boilerplate/core_test.ps1 @@ -0,0 +1,99 @@ +################################################ +# +# INPUT +# +################################################ + +Param( + [hashtable] $params +) + + +################################################ +# +# NOTES +# +################################################ + + +<# + +This script is used for when you want everything to be executed with PowerShell Core + +This one gets called and uses the original scripts through pwsh + +#> + +################################################ +# +# SETTINGS +# +################################################ + +# temporary files to handle objects +$inputFile = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath("$( $Env:Temp )/$( [System.Guid]::NewGuid().toString() )_input.tmp") +$outputFile = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath("$( $Env:Temp )/$( [System.Guid]::NewGuid().toString() )_output.tmp") + +$verb = "Test" + + +################################################ +# +# PROGRAM +# +################################################ + + +#----------------------------------------------- +# FIND OUT CURRENT DIRECTORY +#----------------------------------------------- + +$settingsFile = Get-Item -Path $params.settingsFile + + +#----------------------------------------------- +# CHECK IF PWSH IS INSTALLED +#----------------------------------------------- + +$isPwshInstalled = $false +try { + if ( (pwsh { 1+1 }) -eq 2 ) { + $isPwshInstalled = $true + } +} catch { + #"not there" +} + + +#----------------------------------------------- +# START PWSH PROCESS +#----------------------------------------------- + +If ( $isPwshInstalled -eq $true ) { + + # Save the hashtable to a json file + $htInput | ConvertTo-Json -Compress -Depth 99 | Set-Content -Path $inputFile -Encoding UTF8 + + # Call pwsh with the test file and some parameters and wait for finish + # TODO [ ] Add error handling and maybe timeout? + $coreWrapper = Join-Path -Path $settingsFile.DirectoryName -ChildPath "core_wrapper.ps1" + pwsh -File $coreWrapper -Verb $verb -InputFile $inputFile -OutputFile $outputFile + + # Read return values + $j = Get-Content -Path $outputFile -Raw -Encoding UTF8 | ConvertFrom-Json + + # Convert the PSCustomObject back to a hashtable + $htOutput = [Hashtable]@{} + $j.psobject.properties | ForEach-Object { + $htOutput[$_.Name] = $_.Value + } + + # Remove the temporary json files + Remove-Item -Path $InputFile + Remove-Item -Path $OutputFile + +} else { + + "Sorry, please install pwsh" + +} \ No newline at end of file diff --git a/AptecoPSFramework/boilerplate/core_testsend.ps1 b/AptecoPSFramework/boilerplate/core_testsend.ps1 new file mode 100644 index 0000000..dc5f314 --- /dev/null +++ b/AptecoPSFramework/boilerplate/core_testsend.ps1 @@ -0,0 +1,99 @@ +################################################ +# +# INPUT +# +################################################ + +Param( + [hashtable] $params +) + + +################################################ +# +# NOTES +# +################################################ + + +<# + +This script is used for when you want everything to be executed with PowerShell Core + +This one gets called and uses the original scripts through pwsh + +#> + +################################################ +# +# SETTINGS +# +################################################ + +# temporary files to handle objects +$inputFile = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath("$( $Env:Temp )/$( [System.Guid]::NewGuid().toString() )_input.tmp") +$outputFile = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath("$( $Env:Temp )/$( [System.Guid]::NewGuid().toString() )_output.tmp") + +$verb = "TestSend" + + +################################################ +# +# PROGRAM +# +################################################ + + +#----------------------------------------------- +# FIND OUT CURRENT DIRECTORY +#----------------------------------------------- + +$settingsFile = Get-Item -Path $params.settingsFile + + +#----------------------------------------------- +# CHECK IF PWSH IS INSTALLED +#----------------------------------------------- + +$isPwshInstalled = $false +try { + if ( (pwsh { 1+1 }) -eq 2 ) { + $isPwshInstalled = $true + } +} catch { + #"not there" +} + + +#----------------------------------------------- +# START PWSH PROCESS +#----------------------------------------------- + +If ( $isPwshInstalled -eq $true ) { + + # Save the hashtable to a json file + $htInput | ConvertTo-Json -Compress -Depth 99 | Set-Content -Path $inputFile -Encoding UTF8 + + # Call pwsh with the test file and some parameters and wait for finish + # TODO [ ] Add error handling and maybe timeout? + $coreWrapper = Join-Path -Path $settingsFile.DirectoryName -ChildPath "core_wrapper.ps1" + pwsh -File $coreWrapper -Verb $verb -InputFile $inputFile -OutputFile $outputFile + + # Read return values + $j = Get-Content -Path $outputFile -Raw -Encoding UTF8 | ConvertFrom-Json + + # Convert the PSCustomObject back to a hashtable + $htOutput = [Hashtable]@{} + $j.psobject.properties | ForEach-Object { + $htOutput[$_.Name] = $_.Value + } + + # Remove the temporary json files + Remove-Item -Path $InputFile + Remove-Item -Path $OutputFile + +} else { + + "Sorry, please install pwsh" + +} \ No newline at end of file diff --git a/AptecoPSFramework/boilerplate/core_upload.ps1 b/AptecoPSFramework/boilerplate/core_upload.ps1 new file mode 100644 index 0000000..7d5b9ae --- /dev/null +++ b/AptecoPSFramework/boilerplate/core_upload.ps1 @@ -0,0 +1,99 @@ +################################################ +# +# INPUT +# +################################################ + +Param( + [hashtable] $params +) + + +################################################ +# +# NOTES +# +################################################ + + +<# + +This script is used for when you want everything to be executed with PowerShell Core + +This one gets called and uses the original scripts through pwsh + +#> + +################################################ +# +# SETTINGS +# +################################################ + +# temporary files to handle objects +$inputFile = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath("$( $Env:Temp )/$( [System.Guid]::NewGuid().toString() )_input.tmp") +$outputFile = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath("$( $Env:Temp )/$( [System.Guid]::NewGuid().toString() )_output.tmp") + +$verb = "Upload" + + +################################################ +# +# PROGRAM +# +################################################ + + +#----------------------------------------------- +# FIND OUT CURRENT DIRECTORY +#----------------------------------------------- + +$settingsFile = Get-Item -Path $params.settingsFile + + +#----------------------------------------------- +# CHECK IF PWSH IS INSTALLED +#----------------------------------------------- + +$isPwshInstalled = $false +try { + if ( (pwsh { 1+1 }) -eq 2 ) { + $isPwshInstalled = $true + } +} catch { + #"not there" +} + + +#----------------------------------------------- +# START PWSH PROCESS +#----------------------------------------------- + +If ( $isPwshInstalled -eq $true ) { + + # Save the hashtable to a json file + $htInput | ConvertTo-Json -Compress -Depth 99 | Set-Content -Path $inputFile -Encoding UTF8 + + # Call pwsh with the test file and some parameters and wait for finish + # TODO [ ] Add error handling and maybe timeout? + $coreWrapper = Join-Path -Path $settingsFile.DirectoryName -ChildPath "core_wrapper.ps1" + pwsh -File $coreWrapper -Verb $verb -InputFile $inputFile -OutputFile $outputFile + + # Read return values + $j = Get-Content -Path $outputFile -Raw -Encoding UTF8 | ConvertFrom-Json + + # Convert the PSCustomObject back to a hashtable + $htOutput = [Hashtable]@{} + $j.psobject.properties | ForEach-Object { + $htOutput[$_.Name] = $_.Value + } + + # Remove the temporary json files + Remove-Item -Path $InputFile + Remove-Item -Path $OutputFile + +} else { + + "Sorry, please install pwsh" + +} \ No newline at end of file diff --git a/AptecoPSFramework/boilerplate/core_wrapper.ps1 b/AptecoPSFramework/boilerplate/core_wrapper.ps1 new file mode 100644 index 0000000..634ff7c --- /dev/null +++ b/AptecoPSFramework/boilerplate/core_wrapper.ps1 @@ -0,0 +1,90 @@ +################################################ +# +# INPUT +# +################################################ + +Param( + [String]$Verb # Something like GetMessages + ,[String]$InputFile # A temporary file that is used as input for this script + ,[String]$OutputFile # A temporary file that is used as output for this script +) + + +################################################ +# +# NOTES +# +################################################ + +<# + +This script wraps all calls for PowerShell Core and is already called in pwsh + +#> + + +################################################ +# +# PROGRAM +# +################################################ + +#----------------------------------------------- +# FIND OUT CURRENT DIRECTORY +#----------------------------------------------- + +$settingsFile = Get-Item -Path $params.settingsFile + + +#----------------------------------------------- +# PARSE INPUT JSON FILE AS HASHTABLE +#----------------------------------------------- + +# -AsHashtable works since PS6 +$ht = Get-Content -Path $InputFile -Raw -Encoding UTF8 | ConvertFrom-Json -AsHashtable + + +#----------------------------------------------- +# WRITE HASHTABLE AS OUTPUT JSON FILE +#----------------------------------------------- + +# do something +#$ht.add("abc","def") + +Switch ( $Verb ) { + "GetMessages" { + $scriptFile = Join-Path -Path $settingsFile.DirectoryName -ChildPath "getmessages.ps1" + } + "GetMessageLists" { + $scriptFile = Join-Path -Path $settingsFile.DirectoryName -ChildPath "getmessagelists.ps1" + } + "Preview" { + $scriptFile = Join-Path -Path $settingsFile.DirectoryName -ChildPath "preview.ps1" + } + "Test" { + $scriptFile = Join-Path -Path $settingsFile.DirectoryName -ChildPath "test.ps1" + } + "TestSend" { + $scriptFile = Join-Path -Path $settingsFile.DirectoryName -ChildPath "testsend.ps1" + } + "Upload" { + $scriptFile = Join-Path -Path $settingsFile.DirectoryName -ChildPath "upload.ps1" + } + "Broadcast" { + $scriptFile = Join-Path -Path $settingsFile.DirectoryName -ChildPath "broadcast.ps1" + } + Default { + "The verb is currently not used" # TODO [ ] create an exception instead + } +} + +# Call the corresponding script and wait for finish +. $scriptFile $ht + + +#----------------------------------------------- +# WRITE HASHTABLE AS OUTPUT JSON FILE +#----------------------------------------------- + +$ht | ConvertTo-Json -Compress -Depth 99 | Set-Content -Path $OutputFile -Encoding UTF8 diff --git a/AptecoPSFramework/plugins/emarsys/Plugin.ps1 b/AptecoPSFramework/plugins/emarsys/Plugin.ps1 new file mode 100644 index 0000000..4ee76f0 --- /dev/null +++ b/AptecoPSFramework/plugins/emarsys/Plugin.ps1 @@ -0,0 +1,44 @@ +function Get-CurrentPluginInfo { + [PSCustomObject]@{ + + # identifier for this plugin - please do not change or use this twice + "guid" = "bfd1421f-bfac-4a59-8067-4a4730737061" + + # general information about this plugin + "name" = "emarsys" + "version" = "0.0.1" + "lastUpdate" = "2024-02-26" + "category" = "channel" + "type" = "email" + "stage" = "dev" + + # have a look at ./bin/dependencies if you need more information about how to define this + "dependencies" = [PSCustomObject]@{ + "psScripts" = @() + "psModules" = @( + #"PSOAuth" # TODO make sure this module is loaded + "ConvertStrings" + "ConvertUnixTimestamp" + #"EncryptCredential" + #"SqlServer" + "MeasureRows" + #"SimplySQL" + #"TestCredential" + #"MergePSCustomObject" + ) + "psPackages" = @( + ) + } + + # Supported functions + "functions" = [PSCustomObject]@{ + "mailings" = $true + "lists" = $true + "preview" = $true + "upload" = $true + "broadcast" = $true + "responses" = $false + } + + } +} diff --git a/AptecoPSFramework/plugins/emarsys/Private/classes/Mailing.ps1 b/AptecoPSFramework/plugins/emarsys/Private/classes/Mailing.ps1 new file mode 100644 index 0000000..3102fb0 --- /dev/null +++ b/AptecoPSFramework/plugins/emarsys/Private/classes/Mailing.ps1 @@ -0,0 +1,91 @@ +<# +$m = [Mailing]@{mailingId=123;mailingName="MailingName"} +$m.toString() + +Good hints here: https://xainey.github.io/2016/powershell-classes-and-concepts/ + +# Play around with different constructors +([Mailing]@{mailingId=123;mailingName="abc"}).toString() +([Mailing]::new("123 / abc")).toString() + + +#> +class Mailing { + + #----------------------------------------------- + # PROPERTIES (can be public by default, static or hidden) + #----------------------------------------------- + + [String]$mailingId + [String]$mailingName = "" + hidden [String]$nameConcatChar = " / " + + + #----------------------------------------------- + # CONSTRUCTORS + #----------------------------------------------- + + <# + Notes from: https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_object_creation?view=powershell-7 + You can create an object from a hash table of properties and property values. + + The syntax is as follows: + + []@{ + = + = + } + + This method works only for classes that have a parameterless constructor. The object properties must be public and settable. + + #> + + Mailing () { + + # If we have a nameconcat char in the settings variable, just use it + if ( $script:settings.nameConcatChar ) { + $this.nameConcatChar = $script:settings.nameConcatChar + } + + } # empty default constructor needed to support hashtable constructor + + Mailing ( [String]$mailingId, [String]$mailingName ) { + + $this.mailingId = $mailingId + $this.mailingName = $mailingName + + # If we have a nameconcat char in the settings variable, just use it + if ( $script:settings.nameConcatChar ) { + $this.nameConcatChar = $script:settings.nameConcatChar + } + + } + + Mailing ( [String]$mailingString ) { + + # If we have a nameconcat char in the settings variable, just use it + if ( $script:settings.nameConcatChar ) { + $this.nameConcatChar = $script:settings.nameConcatChar + } + + # Use the 2 in the split as a parameter so it only breaks the string on the first occurence + $stringParts = $mailingString -split $this.nameConcatChar.trim(),2,"simplematch" + $this.mailingId = $stringParts[0].trim() + $this.mailingName = $stringParts[1].trim() + + } + + #----------------------------------------------- + # METHODS + #----------------------------------------------- + + [String] toString() + { + # If we have a nameconcat char in the settings variable, just use it + # if ( $script:settings.nameConcatChar ) { + # $this.nameConcatChar = $script:settings.nameConcatChar + # } + return $this.mailingId, $this.mailingName -join $this.nameConcatChar + } + +} \ No newline at end of file diff --git a/AptecoPSFramework/plugins/emarsys/Private/classes/emarsys.ps1 b/AptecoPSFramework/plugins/emarsys/Private/classes/emarsys.ps1 new file mode 100644 index 0000000..81e7d44 --- /dev/null +++ b/AptecoPSFramework/plugins/emarsys/Private/classes/emarsys.ps1 @@ -0,0 +1,1610 @@ +<# +# https://www.coolgenerator.com/ascii-text-generator +#> + +################################################ +# +# GENERIC CLASSES AND ENUMS +# +################################################ + + +#----------------------------------------------- +# SCOPE +#----------------------------------------------- + +enum DSCPScope { + Global = 0 # global + Local = 1 # list/local + Transactional = 2 # Transactional +} + + +#----------------------------------------------- +# DIGITAL CHANNEL SERVICE PROVIDER +#----------------------------------------------- + +class DCSP { + + [String]$providerName + static [bool]$allowNewFieldCreation = $false + +} + + +#----------------------------------------------- +# FIELDS +#----------------------------------------------- + +class DCSPField { + + [String] $id + [String] $name + [String] $label + [String] $description + [String] $placeholder + [String] $dataType + [String[]] $synonyms + [DSCPScope] $scope = [DSCPScope]::Global + [bool] $required = $false + [String[]] $dependency # Dependency to other field + [DCSPFieldChoice[]] $choices # For selector values + + # empty default constructor needed to support hashtable constructor + DCSPField () { + } + +} + +class DCSPFieldChoice { + + [String] $id + [String] $label + [String] $description + + # empty default constructor needed to support hashtable constructor + DCSPFieldChoice () { + } + +} + + +#----------------------------------------------- +# LISTS +#----------------------------------------------- + +# TODO [ ] think again about this class + +class DCSPList { + + #----------------------------------------------- + # PROPERTIES (can be public by default, static or hidden) + #----------------------------------------------- + + [String]$id + [String]$name = "" + [DateTime]$created + [DateTime]$updated + + hidden [String] $nameConcatChar = " / " + #hidden [String]$type = " / " + + + #----------------------------------------------- + # PUBLIC CONSTRUCTORS + #----------------------------------------------- + + # empty default constructor needed to support hashtable constructor + DCSPList () { + + $this.init() + + } + + + DCSPList ( [String]$inputString ) { + + # If we have a nameconcat char in the settings variable, just use it + $this.init($inputString) + + } + + + #----------------------------------------------- + # HIDDEN CONSTRUCTORS - CHAINING + #----------------------------------------------- + + + [void] init () { + # If we have a nameconcat char in the settings variable, just use it + if ( $script:settings.nameConcatChar ) { + $this.nameConcatChar = $script:settings.nameConcatChar + } + } + + # Used for a minimal input + [void] init ([String]$inputString ) { + + $this.init() + + $stringParts = $inputString -split [regex]::Escape($this.nameConcatChar.trim()),2 + $this.id = $stringParts[0].trim() + $this.name = $stringParts[1].trim() + + } + + + #----------------------------------------------- + # METHODS + #----------------------------------------------- + + [String] toString() + { + return $this.id, $this.name -join $this.nameConcatChar + } + +} + + +#----------------------------------------------- +# MAILINGS - GENERIC +#----------------------------------------------- + +# TODO [ ] think again about this class + +class DCSPMailing { + + #----------------------------------------------- + # PROPERTIES (can be public by default, static or hidden) + #----------------------------------------------- + + [String]$id + [String]$name = "" + [DateTime]$created + + hidden [String] $nameConcatChar = " / " + #hidden [String]$type = " / " + + + #----------------------------------------------- + # PUBLIC CONSTRUCTORS + #----------------------------------------------- + + # empty default constructor needed to support hashtable constructor + DCSPMailing () { + + $this.init() + + } + + + DCSPMailing ( [String]$inputString ) { + + # If we have a nameconcat char in the settings variable, just use it + $this.init($inputString) + + } + + + #----------------------------------------------- + # HIDDEN CONSTRUCTORS - CHAINING + #----------------------------------------------- + + + [void] init () { + # If we have a nameconcat char in the settings variable, just use it + if ( $script:settings.nameConcatChar ) { + $this.nameConcatChar = $script:settings.nameConcatChar + } + } + + # Used for a minimal input + [void] init ([String]$inputString ) { + + $this.init() + + $stringParts = $inputString -split [regex]::Escape($this.nameConcatChar.trim()),2 + $this.id = $stringParts[0].trim() + $this.name = $stringParts[1].trim() + + } + + + #----------------------------------------------- + # METHODS + #----------------------------------------------- + + [String] toString() + { + return $this.id, $this.name -join $this.nameConcatChar + } + +} + + +#----------------------------------------------- +# MAILINGS - EMAIL +#----------------------------------------------- + +enum DCSPMailingsEmailContentTypes { + html = 10 + text = 20 + block = 30 +} + +# Additional properties for email channel +class DCSPMailingsEmail : DCSPMailing { + + #----------------------------------------------- + # PROPERTIES (can be public by default, static or hidden) + #----------------------------------------------- + + [String]$subject + [String]$fromEmail + [String]$fromName + [DCSPMailingsEmailContentTypes]$contentType + +} + + +################################################ +# +# INHERITED CLASSES AND ENUMS +# +################################################ + + +#----------------------------------------------- +# EMARSYS FIELDS +#----------------------------------------------- + +enum EmarsysFieldApplicationTypes { + shorttext = 0 + longtext = 1 + largetext = 2 + date = 3 + url = 4 + numeric = 5 +} + +# TODO [ ] implement language code for fields + +class EmarsysField : DCSPField { + + hidden [Emarsys]$emarsys + [bool]$excludeForExport = $false + + EmarsysField () { + if ( $_.id -in @(27, 28, 29, 32, 33) ) { + $this.excludeForExport = $true + } + } + + delete() { + + # TODO [ ] check if right + + # Call emarsys + $params = @{ + cred = $this.emarsys.cred + uri = "$( $this.emarsys.baseUrl)field/$( $this.id )" + method = "Delete" + } + $res = Invoke-emarsys @params + + } + +} + + +#----------------------------------------------- +# EMARSYS LISTS +#----------------------------------------------- + +class EmarsysList : DCSPList { + + #----------------------------------------------- + # PROPERTIES (can be public by default, static or hidden) + #----------------------------------------------- + + [int] $type + hidden [Emarsys]$emarsys + [PSCustomObject]$raw # the raw source object for this one + + + #----------------------------------------------- + # PUBLIC CONSTRUCTORS + #----------------------------------------------- + + # empty default constructor needed to support hashtable constructor + EmarsysList () { + + # TODO [ ] needed? + #$this.init() + + } + + #----------------------------------------------- + # METHODS + #----------------------------------------------- + + # Returns the number of contacts in a contact list. + [String] count() { + + # Call emarsys + $params = $this.emarsys.defaultParams + @{ + uri = "$( $this.emarsys.baseUrl)contactlist/$( $this.id )/count" + } + [int]$res = Invoke-emarsys @params + + return [int]$res + } + +} + +class EmarsysMailing : DCSPMailingsEmail { + + + #----------------------------------------------- + # PROPERTIES (can be public by default, static or hidden) + #----------------------------------------------- + + hidden [Emarsys]$emarsys + [PSCustomObject]$raw # the raw source object for this one + [String]$language + + + #----------------------------------------------- + # PUBLIC CONSTRUCTORS + #----------------------------------------------- + + # empty default constructor needed to support hashtable constructor + EmarsysMailing () { + + # TODO [ ] needed? + #$this.init() + + } + + #----------------------------------------------- + # METHODS + #----------------------------------------------- + <# + getDetails() { + + # Details + $params = $this.defaultParams + @{ + uri = "$( $this.baseUrl)email/$( $this.id )" + } + Invoke-emarsys @params | select * -ExcludeProperty "html_source","text_source" | Out-GridView + } + #> + + [PSCustomObject] getResponseSummary() { + + # Response summary + $params = $this.emarsys.defaultParams + @{ + uri = "$( $this.emarsys.baseUrl)email/$( $this.id )/responsesummary" # ?launch_id={{launch_id}}&start_date={{start_date}}&end_date={{end_date}} + } + $res = Invoke-emarsys @params + return $res + + } + + [PSCustomObject] getLaunches() { + + $body = @{ + emailId = $this.id # html|text|mobile + } + $bodyJson = ConvertTo-Json -InputObject $body -Depth 20 + + # Call emarsys + $params = $this.emarsys.defaultParams + @{ + uri = "$( $this.emarsys.baseUrl)email/getlaunchesofemail" + method = "Post" + body = $bodyJson + verbose = $true + } + $res = Invoke-emarsys @params + return $res + + } + + [PSCustomObject] getDeliveryStatus() { + return getDeliveryStatus(0) + } + + [PSCustomObject] getDeliveryStatus([int]$launchId) { + + # https://dev.emarsys.com/v2/email-campaign-life-cycle/query-delivery-status + + $body = @{ + emailId = $this.id + #lastId + #allowNotFinished + } + + # Add launch id if not zero + if ( $launchId -gt 0 ) { + $body | Add-Member -MemberType NoteProperty -Name "launchId" -Value $launchId + } + + $bodyJson = ConvertTo-Json -InputObject $body -Depth 20 + + # Call emarsys + $params = $this.emarsys.defaultParams + @{ + uri = "$( $this.emarsys.baseUrl)email/getdeliverystatus" + method = "Post" + body = $bodyJson + verbose = $true + } + $res = Invoke-emarsys @params + return $res + + } + + [PSCustomObject] getPreview() { + + return $this.getPreview("html") + + } + + # put in html, text, mobile + [PSCustomObject] getPreview([String]$version) { + + # https://dev.emarsys.com/v2/email-campaign-life-cycle/preview-email-campaign-contents + + $body = @{ + version = $version # html|text|mobile + } + $bodyJson = ConvertTo-Json -InputObject $body -Depth 20 + + # Call emarsys + $params = $this.emarsys.defaultParams + @{ + uri = "$( $this.emarsys.baseUrl)email/$( $this.id )/preview" + method = "Post" + body = $bodyJson + verbose = $true + } + $res = Invoke-emarsys @params + return $res + + } + + [PSCustomObject] sendTest([String]$subject, [EmarsysList]$list) { + + # https://dev.emarsys.com/v2/email-campaign-life-cycle/send-a-test-email + + # TODO [ ] implement recipientlist, filte_id and contactlist first + + $body = @{ + subject = $subject + + # Multiple values are allowed, separated by a comma without whitespace. + # Provide either a recipientlist, filter_id or contactlist_id. Do not combine. + recipientlist = $list.id + #filter_id = 0 + #contactlist_id = 0 + } + $bodyJson = ConvertTo-Json -InputObject $body -Depth 20 + + # Call emarsys + $params = $this.emarsys.defaultParams + @{ + uri = "$( $this.emarsys.baseUrl)email/$( $this.id )/sendtestmail" + method = "Post" + body = $bodyJson + verbose = $true + } + $res = Invoke-emarsys @params + return $res + + } + + # Use this endpoint to ask for response data + # then start polling downloadResponses within 2 minutes + # the result is available for 2 hourse + [int] getResponses([String]$type) { + + return $this.emarsys.getResponses($type,$this.id) + + # https://dev.emarsys.com/v2/email-campaign-life-cycle/preview-email-campaign-contents + + # TODO [ ] Put the type in an enum + <# + $body = @{ + "type" = $type # opened, not_opened, received, clicked, not_clicked, bounced, hard_bounced, soft_bounced, block_bounced + #"start_date" = "YYYY-MM-DD" + #"end_date" = "YYYY-MM-DD" + "campaign_id" = $this.id # optional + } + $bodyJson = ConvertTo-Json -InputObject $body -Depth 20 + + # Call emarsys + $params = $this.emarsys.defaultParams + @{ + uri = "$( $this.emarsys.baseUrl)email/responses" + method = "Post" + body = $bodyJson + verbose = $true + } + $res = Invoke-emarsys @params + return $res.id +#> + } + + [PSCustomObject] pollResponseResults([int]$queryId) { + + return $this.emarsys.pollResponseResults($queryId) + <# + # Response summary + $params = $this.emarsys.defaultParams + @{ + uri = "$( $this.emarsys.baseUrl)email/$( $queryId )/responses" + } + $res = Invoke-emarsys @params + return $res + #> + } + +} + + +#----------------------------------------------- +# EXPORTS +#----------------------------------------------- + +class EmarsysExport { + + + #----------------------------------------------- + # PROPERTIES (can be public by default, static or hidden) + #----------------------------------------------- + + hidden [Emarsys]$emarsys + [PSCustomObject]$raw # the raw source object for this one + + [EmarsysField[]]$fields + [EmarsysList]$list + [String]$outputFolder + + [int]$exportId + + [String]$status + [DateTime]$startTime + [DateTime]$endTime + #[int]$offset = 0 + hidden [int]$limit = 10000000 #10 #10000000 # TODO [ ] test limit + [int]$totalSeconds = 0 + + hidden [String]$filename + hidden [String[]]$exportFiles + hidden [Timers.Timer]$timer + [bool] $alreadyDownloaded = $false + + + #----------------------------------------------- + # PUBLIC CONSTRUCTORS + #----------------------------------------------- + + # empty default constructor needed to support hashtable constructor + EmarsysExport () { + $this.init() + } + + #----------------------------------------------- + # METHODS + #----------------------------------------------- + + hidden [void] init() { + $this.startTime = [DateTime]::Now + } + + [String[]] getFiles() { + return $this.exportFiles + } + + [void] updateStatus () { + + $params = $this.emarsys.defaultParams + @{ + uri = "$( $this.emarsys.baseUrl )export/$( $this.exportId )" + } + $exportStatus = Invoke-emarsys @params + #Write-Verbose ( $exportStatus | ConvertTo-Json ) + $this.status = $exportStatus.status + $this.raw = $exportStatus + + if ( $exportStatus.status -eq "done" ) { + $this.filename = $exportStatus.file_name + $this.endTime = [DateTime]::Now + $t = New-TimeSpan -Start $this.startTime -End $this.endTime + $this.totalSeconds = $t.TotalSeconds + + } + + } + + [void] autoUpdate() { + $this.autoUpdate($false) + } + + [void] autoUpdate([bool]$downloadImmediatly) { + + # Create a timer object with a specific interval and a starttime + $this.timer = New-Object -Type Timers.Timer + $this.timer.Interval = 20000 # milliseconds, the interval defines how often the event gets fired + $timerTimeout = 600 # seconds + + # Register an event for every passed interval + Register-ObjectEvent -InputObject $this.timer -EventName "Elapsed" -SourceIdentifier $this.exportId -MessageData @{ timeout=$timerTimeout; emarsysExport = $this ; downloadImmediatly = $downloadImmediatly } -Action { + + # Input + $emarsysExport = $Event.MessageData.emarsysExport + + # Calculate current timespan + $timeSpan = New-TimeSpan -Start $emarsysExport.startTime -End ( Get-Date ) + + # Check current status + $emarsysExport.updateStatus() + + If ($emarsysExport.status -eq "done" ) { # -or ( $this.raw.type -eq "responses" -and $emarsysExport.status -eq "ready") + + $Sender.stop() + + if ($Event.MessageData.downloadImmediatly) { + $emarsysExport.downloadResult() + } + + } + + # Is timeout reached? Do something! + if ( $timeSpan.TotalSeconds -gt $Event.MessageData.timeout ) { + + # Stop timer now (it is important to do this before the next processes run) + $Sender.Stop() + Write-Host "Done! Timer stopped because timeout reached!" + + } + + } | Out-Null + + # Start the timer + $this.timer.Start() + + } + + [void] downloadResult() { + + # TODO [ ] unregister timer event, if it exists + + # Download file + # TODO [ ] implement offset and limit + # TODO [ ] export contains multiple files + # TODO [ ] calculate time when finishing export + if ( @("ready","done") -contains $this.status ) { + + if ($this.raw.type -eq "contactlist") { + $listCount = $this.list.count() + $rounds = [Math]::Ceiling($listCount/$this.limit) + } else { + $rounds = 1 + } + + for ( $i = 0 ; $i -lt $rounds ; $i++ ) { + # TODO [ ] it looks like there is a bug in offset and limit, so re-visit this later + $offset = $i * $rounds + + # Sometimes the export does not come to the status "done" so we can download it with a fictitous filename + #if ( $this.status -eq "ready" ) { + # $this.filename = "$( [DateTime]::Now.ToString("yyyyMMdd_HHmmss") ).csv" + #} + + # Create the download job + $params = $this.emarsys.defaultParams + @{ + uri = "$( $this.emarsys.baseUrl )export/$( $this.exportId )/data" #?offset=$( $offset )&limit=$( $this.limit )" + outFile = "$( $this.outputFolder )\$( $this.filename )" + } + Invoke-emarsys @params + + # Add to the result + $this.exportFiles += $params.OutFile + } + + # Flag this as already downloaded + $this.alreadyDownloaded = $true + + } + + } + +} + + +################################################ +# +# MAIN CLASS +# +################################################ + + +class Emarsys : DCSP { + + #----------------------------------------------- + # PROPERTIES (can be public by default, static or hidden) + #----------------------------------------------- + + hidden [pscredential]$cred # holds the username and secret + hidden [int]$waitSeconds = 10 + [String]$baseUrl = "https://api.emarsys.net/api/v2/" + [DSCPScope[]]$supportedScopes = @( + [DSCPScope]::Global + #[DSCPScope]::Local + ) + + # Override inherited properties + [String]$providerName = "emarsys" + static [bool]$allowNewFieldCreation = $true + + [PSCustomObject]$defaultParams + hidden [EmarsysExport[]]$exports + + + #----------------------------------------------- + # PUBLIC CONSTRUCTORS + #----------------------------------------------- + + + # empty default constructor needed to support hashtable constructor + Emarsys () { + $this.init() + } + + Emarsys ( [String]$username, [String]$secret ) { + $this.init( $username, $secret ) + } + + Emarsys ( [String]$username, [String]$secret, [String]$baseUrl ) { + $this.init( $username, $secret, $baseUrl) + } + + Emarsys ( [pscredential]$cred ) { + $this.init( $cred ) + } + + Emarsys ( [pscredential]$cred, [String]$baseUrl ) { + $this.init( $cred, $baseUrl ) + } + + #----------------------------------------------- + # HIDDEN CONSTRUCTORS - CHAINING + #----------------------------------------------- + + hidden [void] init () { + + $this.defaultParams = @{ + cred = $this.cred + } + + if ( $script:settings.download.waitSecondsLoop ) { + $this.waitSeconds = $script:settings.download.waitSecondsLoop + } + + #$this.exports = [System.Collections.ArrayList]@() + + } + + hidden [void] init ( [String]$username, [String]$secret ) { + $stringSecure = ConvertTo-SecureString -String ( Get-SecureToPlaintext $secret ) -AsPlainText -Force + $this.cred = [pscredential]::new( $username, $stringSecure ) + $this.init() + } + + hidden [void] init ( [String]$username, [String]$secret, [String]$baseUrl ) { + $this.baseUrl = $baseUrl + $this.init( $username, $secret ) + } + + hidden [void] init ( [pscredential]$cred ) { + $this.cred = $cred + $this.init() + } + + hidden [void] init ( [pscredential]$cred, [String]$baseUrl ) { + $this.baseUrl = $baseUrl + $this.init( $cred ) + } + + + + #----------------------------------------------- + # METHODS + #----------------------------------------------- + + [PSCustomObject] getSettings () { + + $params = $this.defaultParams + @{ + uri = "$( $this.baseUrl)settings" + } + + $res = Invoke-emarsys @params + return $res + + } + + [string] newField([String]$fieldname, [EmarsysFieldApplicationTypes]$dataType) { + + # TODO [] implement this one https://dev.emarsys.com/v2/fields/create-a-field + + $body = @{ + name = $fieldname + application_type = $dataType # shorttext|longtext|largetext|date|url|numeric + #string_id = "" # optional otherwise autogenerated + } + $bodyJson = ConvertTo-Json -InputObject $body -Depth 20 + + # Call emarsys + $params = $this.defaultParams + @{ + uri = "$( $this.baseUrl)field" + method = "Post" + body = $bodyJson + verbose = $true + } + $res = Invoke-emarsys @params + + # return the new identifier of the field + return $res + + } + + [PSCustomObject] getFields () { + return getFields($true) + } + + + + [EmarsysField[]] getFields ([bool]$loadDetails) { + + # Call emarsys + $params = $this.defaultParams + @{ + uri = "$( $this.baseUrl)field" + } + $res = Invoke-emarsys @params + + + # Transform result to objects + $fields = [System.Collections.ArrayList]@() + $res | ForEach { + + $f = $_ + + $choice = [System.Collections.ArrayList]@() + + if ( $loadDetails ) { + + # list fields choices + if ( $f.application_type -eq "singlechoice") { + $params = @{ + cred = $this.cred + uri = "$( $this.baseUrl)field/$( $f.id )/choice" + } + $choices = Invoke-emarsys @params + $choices | ForEach { + $c = $_ + [void]$choice.Add([DCSPFieldChoice]@{ + "id" = $c.id + "label" = $c.choice + }) + } + # TODO [ ] check bit_position in return data + } + + # TODO [ ] check multiple choice which is called with /choices + + } + + $fields.Add([EmarsysField]@{ + "emarsys" = $this + "id" = $f.id + "name" = $f.string_id + "label" = $f.name + "dataType" = $f.application_type + #"scope" = [DSCPScope]::Global + "choices" = $choice + }) + + $choice.Clear() + + } + + + + # Return the results + return $fields + + } + + [EmarsysList[]] getLists () { + + # https://dev.emarsys.com/v2/contact-lists/count-contacts-in-a-contact-list + # TODO [ ] implement as classes with create, rename, delete, count, list contacts, list contacts data, add contacts, lookup(?) + + # Call emarsys + $params = $this.defaultParams + @{ + uri = "$( $this.baseUrl)contactlist" + } + $res = Invoke-emarsys @params + + + # Transform result to objects + $lists = [System.Collections.ArrayList]@() + $res | ForEach { + + $l = $_ + + [void]$lists.Add([EmarsysList]@{ + "emarsys" = $this + "id" = $l.id + "name" = $l.name + "created" = $l.created + "type" = $l.type + "raw" = $l + }) + + } + + return $lists + + } + + + [PSCustomObject] getSegments () { + + # TODO [ ] implement as classes + + # Call emarsys + $params = $this.defaultParams + @{ + uri = "$( $this.baseUrl)filter" + } + $res = Invoke-emarsys @params + return $res + + } + + [PSCustomObject] getSources () { + + # TODO [ ] implement as classes + + # Call emarsys + $params = $this.defaultParams + @{ + uri = "$( $this.baseUrl)source" + } + $res = Invoke-emarsys @params + return $res + + } + + [PSCustomObject] createContact ([String]$keyId, [String]$contactListId, [System.Collections.ArrayList]$arr) { + + # TODO [ ] currently only for test/dev + + $body = [PSCustomObject]@{ + "key_id" = $keyId + "contacts" = $arr + "contact_list_id" = $contactListId + } + + # Call emarsys + $params = $this.defaultParams + @{ + uri = "$( $this.baseUrl )contact/?create_if_not_exists=1" + method = "Put" + body = ConvertTo-Json -InputObject $body -Depth 20 + } + $res = Invoke-emarsys @params + return $res + + } + + [PSCustomObject] deleteContact ([String]$keyId, [System.Collections.ArrayList]$arr) { + + # TODO [ ] currently only for test/dev + + $body = [PSCustomObject]@{ + "key_id" = $keyId + "$( $keyId )" = $arr + #"contact_list_id" = $contactListId + } + + # Call emarsys + $params = $this.defaultParams + @{ + uri = "$( $this.baseUrl )contact/delete" + method = "Post" + body = ConvertTo-Json -InputObject $body -Depth 20 + } + $res = Invoke-emarsys @params + return $res + + } + + [PSCustomObject] deleteContactFromList ([String]$keyId, [Int]$contactListId, [System.Collections.ArrayList]$arr) { + + # TODO [ ] currently only for test/dev + + $body = [PSCustomObject]@{ + "key_id" = $keyId + "$( $keyId )" = $arr + "contact_list_id" = $contactListId + } + + # Call emarsys + $params = $this.defaultParams + @{ + uri = "$( $this.baseUrl )contact/delete" + method = "Post" + body = ConvertTo-Json -InputObject $body -Depth 20 + } + $res = Invoke-emarsys @params + return $res + + } + + [PSCustomObject] createList ([String]$keyId, [String]$name, [String]$description) { + + # TODO [ ] currently only for test/dev + + $body = [PSCustomObject]@{ + "key_id" = $keyId + "name" = $name + "description" = $description + "external_ids" = [Array]@() + } + + # Call emarsys + $params = $this.defaultParams + @{ + uri = "$( $this.baseUrl )contactlist" + method = "Post" + body = ConvertTo-Json -InputObject $body -Depth 20 + } + $res = Invoke-emarsys @params + return $res + + } + + + [PSCustomObject] fetchListContacts ([String]$listId) { + + # TODO [ ] currently only for test/dev + # TODO [ ] will be deprecated End of 2024 + + # Call emarsys + $params = $this.defaultParams + @{ + uri = "$( $this.baseUrl )contactlist/$( $listId )/contacts" + method = "Get" + } + $res = Invoke-emarsys @params + return $res + + } + + [PSCustomObject] getContactData ([String]$keyId, [System.Collections.ArrayList]$fields, [System.Collections.ArrayList]$keyValues) { + + # TODO [ ] currently only for test/dev + + $body = [PSCustomObject]@{ + "keyId" = $keyId + "fields" = $fields + "keyValues" = $keyValues + } + + # Call emarsys + $params = $this.defaultParams + @{ + uri = "$( $this.baseUrl )contact/getdata" + method = "Post" + body = ConvertTo-Json -InputObject $body -Depth 20 + + } + $res = Invoke-emarsys @params + return $res + + } + + + [PSCustomObject] countList ([String]$listId) { + + # TODO [ ] currently only for test/dev + + # Call emarsys + $params = $this.defaultParams + @{ + uri = "$( $this.baseUrl )contactlist/$( $listId )/count" #https://api.emarsys.net/api/v2/contactlist/{listId}/count + method = "Get" + } + $res = Invoke-emarsys @params + return $res + + } + + + + [EmarsysMailing[]] getEmailCampaigns () { + + # https://dev.emarsys.com/v2/contact-lists/count-contacts-in-a-contact-list + # TODO [ ] implement as classes with the toString-function, list tracked links, list sections, preview + + # Call emarsys + $params = $this.defaultParams + @{ + uri = "$( $this.baseUrl)email" # ?status={{status}}&launched={{launched}}&contactlist={{contactlist}}&showdeleted={{showdeleted}}&fromdate={{fromdate}}&todate={{todate}}&root_campaign_id={{root_campaign_id}}&template={{template}}&content_type={{content_type}}&campaign_type={{campaign_type}}&parent_campaign_id={{parent_campaign_id}}&behavior_channel={{behavior_channel}}&email_category={{email_category}} + } + $res = Invoke-emarsys @params + + # Transform result to objects + $campaigns = [System.Collections.ArrayList]@() + $res | ForEach { + + $c = $_ + + [void]$campaigns.Add([EmarsysMailing]@{ + + "id" = $c.id + "name" = $c.name + "created" = $c.created + + "subject" = $c.subject + "fromEmail" = $c.fromemail + "fromName" = $c.fromname + "contentType" = [DCSPMailingsEmailContentTypes]::($c."content_type") + + "language" = $c.language + "emarsys" = $this + "raw" = $c + + }) + + } + + return $campaigns + + } + + # TODO [ ] implement media database if needed + + [PSCustomObject] getConditionalTextRules () { + + # TODO [ ] implement as classes + + # Call emarsys + $params = $this.defaultParams + @{ + uri = "$( $this.baseUrl)condition" + } + $res = Invoke-emarsys @params + return $res + + } + + [PSCustomObject] getEmailTemplates () { + + # TODO [ ] implement as classes + + # Call emarsys + $params = $this.defaultParams + @{ + uri = "$( $this.baseUrl)email/templates" + } + $res = Invoke-emarsys @params + return $res + + } + + [PSCustomObject] getLinkCategories () { + + # TODO [ ] implement as classes + + # Call emarsys + $params = $this.defaultParams + @{ + uri = "$( $this.baseUrl)settings/linkcategories" + } + $res = Invoke-emarsys @params + return $res + + } + + [PSCustomObject] getExternalEvents () { + + # TODO [ ] implement as classes + + # Call emarsys + $params = $this.defaultParams + @{ + uri = "$( $this.baseUrl)event" + } + $res = Invoke-emarsys @params + return $res + + } + + [PSCustomObject] getAutomationCenterPrograms () { + + # TODO [ ] implement as classes + + # Call emarsys + $params = $this.defaultParams + @{ + uri = "$( $this.baseUrl)ac/programs" + } + $res = Invoke-emarsys @params + return $res + + } + + [PSCustomObject] getAutoImportProfiles () { + + # TODO [ ] implement as classes + + # Call emarsys + $params = $this.defaultParams + @{ + uri = "$( $this.baseUrl)settings/autoimports" + } + $res = Invoke-emarsys @params + return $res + + } + + [PSCustomObject] getEmailCategories () { + + # TODO [ ] implement as classes + + # Call emarsys + $params = $this.defaultParams + @{ + uri = "$( $this.baseUrl)emailcategory" + } + $res = Invoke-emarsys @params + return $res + + } + + + + [EmarsysExport[]] downloadContactList ( [EmarsysList]$list, [String]$outputFolder ) { + + $exportJobs = [System.Collections.ArrayList]@() + + # split the fields automatically + # TODO [ ] find out if the primary key is always included + + $fields = $this.getFields($false) | where { $_.excludeForExport -eq $false } + + # paging through fields and create exports + $count = $fields.count + $maxFields = 20 # max from emarsys + $rounds = [System.Math]::Ceiling($count/$maxFields) + for ( $i = 0 ; $i -lt $rounds ; $i++ ) { + $start = $i * $maxFields + $end = ( ( $i + 1 ) * $maxFields ) -1 + $exportFields = $fields[$start..$end] + $emarsysExport = $this.downloadContactList($list,$exportFields,$outputFolder) + $exportJobs.Add( $emarsysExport ) + #$this.exports += $emarsysExport + } + + #$this.exports.AddRange($exportJobs) + + return $exportJobs + + } + + [EmarsysExport[]] getExports() { + return $this.exports + } + + # Download the contacts synchronously + [EmarsysExport] downloadContactList ( [EmarsysList]$list, [EmarsysField[]]$fields, [String]$outputFolder ) { + + # TODO [ ] implement as classes + # TODO [ ] make delimiter available as enum + # TODO [ ] implement language + + $exportFields = $fields | where { $_.excludeForExport -eq $false } + + # Create export + $body = @{ + distribution_method = "local" + contactlist = $list.id + contact_fields = $exportFields.id # # field ids -> max 20 columns, exclude of 27, 28, 29, 32 and 33 + delimiter = ";" # ,|; + add_field_names_header = 1 + #language = "de" + } + + # Call emarsys to create export job + $params = $this.defaultParams + @{ + uri = "$( $this.baseUrl )email/getcontacts" + method = "Post" + body = ConvertTo-Json -InputObject $body -Depth 20 + } + $exportId = Invoke-emarsys @params + + # Create the export object now + $export = ( [EmarsysExport]@{ + + "emarsys" = $this + "raw" = $exportId + + "outputFolder" = $outputFolder + "fields" = $fields + "list" = $list + + "exportId" = $exportId.id + + }) + + $this.exports += $export + + return $export + } + + [EmarsysExport] downloadSegment ([String]$outputFolder) { + # https://dev.emarsys.com/v2/contact-and-email-data/export-a-segment + # TODO [ ] implement this + return [EmarsysExport]@{} + } + + [EmarsysExport] downloadRegistrations ([String]$outputFolder) { + # https://dev.emarsys.com/v2/contact-and-email-data/export-contact-registrations + # TODO [ ] implement this + return [EmarsysExport]@{} + } + + + [EmarsysExport] downloadResponses ([String]$outputFolder) { + + # TODO [ ] implement the response download + + # https://dev.emarsys.com/v2/contact-and-email-data/export-responses + $body = @{ + distribution_method = "local" + time_range = @( + ( Get-Date -Year 2022 -Month 2 -Day 15 -Hour 0 -Minute 0 -Second 0 ).ToString("yyyy-MM-dd HH:mm:ss") + ( Get-Date -Year 2022 -Month 2 -Day 15 -Hour 23 -Minute 59 -Second 59 ).ToString("yyyy-MM-dd HH:mm:ss") + #[DateTime]::UtcNow.ToString("yyyy-MM-dd HH:mm:ss") + ) #YYYY-MM-DD HH-SS + contact_fields = @( + 1 + 3 # 3 is email + ) # the field identifiers to include in the export. + sources = @( + #"trackable_links" + #"registration_forms" + #"tell_a_friend" + #"contact_us" + #"change_profile" + "unsubscribe" + "mail_open" + #"complaint" + ) + analysis_fields = @( + #1 # Campaign title + #2 # Section header + #3 # Section group + #4 # Link title + 5 # URL + 8 # Time + #12 # Campaign identifier + #13 # Version name + #14 # Campaign category + 15 # Link category + ) + + # optional + #email_id = 100146526 # The identifier of the email campaign. Returns the contact's responses to the email. + #contactlist = 786367148 #$list.id # The identifier of the contact list to filter the results. + delimiter = ";" # ,|; + add_field_names_header = 1 # Determines whether to insert a header row into the CSV file. + language = "en" + } + + # Call emarsys to create export job + $params = $this.defaultParams + @{ + uri = "$( $this.baseUrl )email/getresponses" + method = "Post" + body = ConvertTo-Json -InputObject $body -Depth 20 + } + $exportId = Invoke-emarsys @params + + # TODO [ ] load the body / parameters into the Export object, too? + + # Create the export object now + $export = ( [EmarsysExport]@{ + + "emarsys" = $this + "raw" = $exportId + "outputFolder" = "." + + #"fields" = $fields + #"list" = $list + + "exportId" = $exportId.id + + }) + + $this.exports += $export + + return $export + + } + + [int] getResponses([String]$type) { + return $this.getResponses($type,0) + } + + # Use this endpoint to ask for response data + # then start polling downloadResponses within 2 minutes + # the result is available for 2 hourse + [int] getResponses([String]$type, [int]$campaignId) { + + # https://dev.emarsys.com/v2/email-campaign-life-cycle/preview-email-campaign-contents + + # TODO [ ] Put the type in an enum + + $body = @{ + "type" = $type # opened, not_opened, received, clicked, not_clicked, bounced, hard_bounced, soft_bounced, block_bounced + #"start_date" = "YYYY-MM-DD" + #"end_date" = "YYYY-MM-DD" + #"campaign_id" = $this.id # optional + } + if ( $campaignId -gt 0 ) { + $body | Add-Member -MemberType NoteProperty -Name "campaign_id" -Value $campaignId + } + $bodyJson = ConvertTo-Json -InputObject $body -Depth 20 + + # Call emarsys + $params = $this.defaultParams + @{ + uri = "$( $this.baseUrl)email/responses" + method = "Post" + body = $bodyJson + verbose = $true + } + $res = Invoke-emarsys @params + return $res.id + + } + + [PSCustomObject] pollResponseResults([int]$queryId) { + + # Response summary + $params = $this.defaultParams + @{ + uri = "$( $this.baseUrl)email/$( $queryId )/responses" + } + $res = Invoke-emarsys @params + return $res + + } + +} + + +################################################ +# +# OTHER FUNCTIONS +# +################################################ + +function Invoke-emarsys { + + [CmdletBinding()] + param ( + [Parameter(Mandatory=$false)][pscredential]$cred # securestring containing username as user and secret as password + ,[Parameter(Mandatory=$false)][System.Uri]$uri = "https://api.emarsys.net/api/v2/" # default url to use + ,[Parameter(Mandatory=$false)][String]$method = "Get" + ,[Parameter(Mandatory=$false)][String]$outFile = "" + ,[Parameter(Mandatory=$false)][System.Object]$body = $null + ) + + begin { + + + #----------------------------------------------- + # AUTH + #----------------------------------------------- + + <# + + example for header + + X-WSSE: UsernameToken + Username="customer001", + PasswordDigest="ZmI2ZmQ0MDIxYmNwQjcxNDkxY2RjNDNiMWExNjFkZA==", + Nonce="d36e316282959a9ed4c72351497a717f", + Created="2014-03-20T12:51:45Z" + + source: https://dev.emarsys.com/v2/before-you-start/authentication + api endpoints: https://trunk-int.s.emarsys.com/api-demo/#tab-customer + + other urls + https://dev.emarsys.com/v2/emarsys-developer-hub/what-is-the-emarsys-api + #> + + # Extract credentials + $secret = $cred.GetNetworkCredential().Password + $username = $cred.UserName + + # Create nonce + $randomStringAsHex = Get-RandomString -length 16 | Format-Hex + $nonce = Get-StringfromByte -byteArray $randomStringAsHex.Bytes + + # Format date + $date = [datetime]::UtcNow.ToString("o") + + # Create password digest + $stringToSign = $nonce + $date + $secret + $sha1 = Get-StringHash -inputString $stringToSign -hashName "SHA1" + $passwordDigest = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($sha1)) + + # Combine Escher XWSSE header + $xwsseArr = [System.Collections.ArrayList]@() + [void]$xwsseArr.Add("UsernameToken Username=""$( $username )""") + [void]$xwsseArr.Add("PasswordDigest=""$( $passwordDigest )""") + [void]$xwsseArr.Add("Nonce=""$( $nonce )""") + [void]$xwsseArr.Add("Created=""$( $date )""") + + # Setup content type + $contentType = "application/json;charset=utf-8" + #$xwsseArr.Add("Content-type=""$( $contentType )""") # take this out possibly + + # Join Escher XWSSE together + $xwsse = $xwsseArr -join ", " + #$xwsse + + + #----------------------------------------------- + # HEADER + #----------------------------------------------- + + $header = @{ + "X-WSSE"=$xwsse + "X-Requested-With"= "XMLHttpRequest" + } + + } + + process { + + + $params = @{ + "Uri" = $uri + "Method" = $method + "Headers" = $header + "ContentType" = $contentType + "Verbose" = $true + } + + if ( $body -ne $null ) { + $params += @{ + "Body" = $body + } + } + + if ( $outFile -ne "" ) { + $params += @{ + "OutFile" = $outFile + } + } + + $result = Invoke-RestMethod @params #-UseBasicParsing + + } + + end { + + if ( $outFile -ne "" ) { + + $outFile + + } else { + + if ( $result.replyCode -eq 0 <# -and $result.replyText -eq "OK" #> ) { + + $result.data + + } else { + # Errors see here: https://dev.emarsys.com/v2/response-codes/http-400-errors + Write-Log -message "Got back $( $result.replyText ) from call to url $( $uri ), throwing exception" + throw [System.IO.InvalidDataException] + + } + + } + + } + +} \ No newline at end of file diff --git a/AptecoPSFramework/plugins/emarsys/Private/emarsys/Invoke-EmarsysLogin.ps1 b/AptecoPSFramework/plugins/emarsys/Private/emarsys/Invoke-EmarsysLogin.ps1 new file mode 100644 index 0000000..350b748 --- /dev/null +++ b/AptecoPSFramework/plugins/emarsys/Private/emarsys/Invoke-EmarsysLogin.ps1 @@ -0,0 +1,45 @@ + + + + function Invoke-EmarsysLogin { + [CmdletBinding()] + param ( + #[Parameter(Mandatory=$false)][Hashtable] $InputHashtable + #[Parameter(Mandatory=$false)][Switch] $DebugMode = $false + ) + + begin { + + + + } + + process { + + # if the class is not already initialised, do it now + If ( $null -eq $Script:variableCache.emarsys ) { + + $stringSecure = ConvertTo-SecureString -String ( Convert-SecureToPlaintext $Script:settings.login.secret ) -AsPlainText -Force + $cred = [pscredential]::new( $Script:settings.login.username, $stringSecure ) + + # Read static attribute + #[Emarsys]::allowNewFieldCreation + + # Create emarsys object + $emarsys = [Emarsys]::new($cred,$settings.base) + + # Save the emarsys object in cache + $Script:variableCache.Add("emarsys",$emarsys) + + } + + } + + end { + + } + + } + + + \ No newline at end of file diff --git a/AptecoPSFramework/plugins/emarsys/Private/helper/Get-StringFromByte.ps1 b/AptecoPSFramework/plugins/emarsys/Private/helper/Get-StringFromByte.ps1 new file mode 100644 index 0000000..a2fb9ce --- /dev/null +++ b/AptecoPSFramework/plugins/emarsys/Private/helper/Get-StringFromByte.ps1 @@ -0,0 +1,24 @@ + +# transform bytes into hexadecimal string (e.g. for hash values) + + +function Get-StringFromByte() { + + [CmdletBinding()] + param ( + [Parameter(Mandatory=$false)][Byte[]]$byteArray = "https://api.emarsys.net/api/v2/" # default url to use + ) + + $stringBuilder = "" + $byteArray | ForEach { $stringBuilder += $_.ToString("x2") } + + $stringBuilder + +} + +# Deprecated function call +function getStringFromByte($byteArray) { + + Get-StringFromByte -byteArray $byteArray + +} \ No newline at end of file diff --git a/AptecoPSFramework/plugins/emarsys/Public/Get-PluginDebug.ps1 b/AptecoPSFramework/plugins/emarsys/Public/Get-PluginDebug.ps1 new file mode 100644 index 0000000..e04b1a7 --- /dev/null +++ b/AptecoPSFramework/plugins/emarsys/Public/Get-PluginDebug.ps1 @@ -0,0 +1,27 @@ + + +function Get-PluginDebug { + + [CmdletBinding()] + param ( + #[Parameter(Mandatory=$false)][Hashtable] $InputHashtable + #[Parameter(Mandatory=$false)][String] $GroupId + ) + + begin { + + } + process { + + $Script:pluginDebug + + } + + end { + + } + +} + + + diff --git a/AptecoPSFramework/plugins/emarsys/Public/emarsys/Add-Contact.ps1 b/AptecoPSFramework/plugins/emarsys/Public/emarsys/Add-Contact.ps1 new file mode 100644 index 0000000..71c9121 --- /dev/null +++ b/AptecoPSFramework/plugins/emarsys/Public/emarsys/Add-Contact.ps1 @@ -0,0 +1,59 @@ + +function Add-Contact { + [CmdletBinding()] + param ( + #[Parameter(Mandatory=$false)][Hashtable] $InputHashtable + [Parameter(Mandatory=$true)][Array] $Add + ) + + begin { + + Invoke-EmarsysLogin + + } + + process { + + #| Out-GridView -PassThru | Select -first 20 +# $fields | Out-GridView +# #$fields | Export-Csv -Path ".\fields.csv" -Encoding Default -NoTypeInformation -Delimiter "`t" +# #$fields | Select @{name="field_id";expression={ $_.id }}, @{name="fieldname";expression={$_.name}} -ExpandProperty choices | Export-Csv -Path ".\fields_choices.csv" -Encoding Default -NoTypeInformation -Delimiter "`t" + +# $c = Invoke-emarsys -cred $cred -uri "$( $settings.base )field/translate/de" -method Get + + $emarsys = $Script:variableCache.emarsys + + #$fields = $emarsys.getFields($false) + + $a = [System.Collections.ArrayList]@( +<# + [PSCustomObject]@{ + "1" = "Florian" + "2" = "von Bracht" + "3" = "florian.von.bracht@apteco.tld" + "31" = $null + } + + [PSCustomObject]@{ + "1" = "Florian" + "2" = "Friedrichs" + "3" = "florian.friedrichs@apteco.tld" + "31" = $null + } +#> + ) + $a.AddRange($Add) + + $res = $emarsys.createContact("3","890495209", $a) + + $res + + } + + end { + + } + +} + + diff --git a/AptecoPSFramework/plugins/emarsys/Public/emarsys/Add-ContactList.ps1 b/AptecoPSFramework/plugins/emarsys/Public/emarsys/Add-ContactList.ps1 new file mode 100644 index 0000000..7be7e5b --- /dev/null +++ b/AptecoPSFramework/plugins/emarsys/Public/emarsys/Add-ContactList.ps1 @@ -0,0 +1,38 @@ + +function Add-ContactList { + [CmdletBinding()] + param ( + #[Parameter(Mandatory=$false)][Hashtable] $InputHashtable + #[Parameter(Mandatory=$false)][Switch] $DebugMode = $false + ) + + begin { + + Invoke-EmarsysLogin + + } + + process { + + #| Out-GridView -PassThru | Select -first 20 +# $fields | Out-GridView +# #$fields | Export-Csv -Path ".\fields.csv" -Encoding Default -NoTypeInformation -Delimiter "`t" +# #$fields | Select @{name="field_id";expression={ $_.id }}, @{name="fieldname";expression={$_.name}} -ExpandProperty choices | Export-Csv -Path ".\fields_choices.csv" -Encoding Default -NoTypeInformation -Delimiter "`t" + +# $c = Invoke-emarsys -cred $cred -uri "$( $settings.base )field/translate/de" -method Get + + $emarsys = $Script:variableCache.emarsys + # $keyId, [String]$name, [String]$description + $newList = $emarsys.createList("3", "TestList Apteco 202403 v2", "List to test the performance with different usecases" ) # 3 = email + + $newList + + } + + end { + + } + +} + + diff --git a/AptecoPSFramework/plugins/emarsys/Public/emarsys/Get-Campaigns.ps1 b/AptecoPSFramework/plugins/emarsys/Public/emarsys/Get-Campaigns.ps1 new file mode 100644 index 0000000..61a9f2a --- /dev/null +++ b/AptecoPSFramework/plugins/emarsys/Public/emarsys/Get-Campaigns.ps1 @@ -0,0 +1,55 @@ +function Get-Campaigns { + [CmdletBinding()] + param ( + [Parameter(Mandatory=$false)][Switch] $Launched = $false + #[Parameter(Mandatory=$false)][Switch] $DebugMode = $false + ) + + begin { + + Invoke-EmarsysLogin + + } + + process { + + #| Out-GridView -PassThru | Select -first 20 +# $fields | Out-GridView +# #$fields | Export-Csv -Path ".\fields.csv" -Encoding Default -NoTypeInformation -Delimiter "`t" +# #$fields | Select @{name="field_id";expression={ $_.id }}, @{name="fieldname";expression={$_.name}} -ExpandProperty choices | Export-Csv -Path ".\fields_choices.csv" -Encoding Default -NoTypeInformation -Delimiter "`t" + +# $c = Invoke-emarsys -cred $cred -uri "$( $settings.base )field/translate/de" -method Get + + # TODO remove this later + $stringSecure = ConvertTo-SecureString -String ( Convert-SecureToPlaintext $Script:settings.login.secret ) -AsPlainText -Force + $cred = [pscredential]::new( $Script:settings.login.username, $stringSecure ) + + #$emarsys = $Script:variableCache.emarsys + + # Change parameters + If ( $Launched -eq $true ) { + $launchedParam = 1 + } else { + $launchedParam = 0 + } + + # List email campaigns + $campaigns = Invoke-emarsys -cred $cred -uri "$( $Script:settings.base )email/launched=$( $launchedParam )&fromdate=2023-02-22" -method GET #-body $body + #$campaigns.Count + + # Choose email campaign + #$campaign = $campaigns | where { $_.status -in @('3','-3') } | where { $_.name -like "*Deu – Pflicht – Willkommen*" } | Out-GridView -PassThru + #$campaign = $campaigns | where { $_.status -eq '-3' } | Out-GridView -PassThru + #$campaign = $campaigns | where { $_.id -eq '10558554' } + + $campaigns + + } + + end { + + } + +} + + diff --git a/AptecoPSFramework/plugins/emarsys/Public/emarsys/Get-ContactData.ps1 b/AptecoPSFramework/plugins/emarsys/Public/emarsys/Get-ContactData.ps1 new file mode 100644 index 0000000..c76c1e6 --- /dev/null +++ b/AptecoPSFramework/plugins/emarsys/Public/emarsys/Get-ContactData.ps1 @@ -0,0 +1,49 @@ + + + + + +function Get-ContactData { + [CmdletBinding()] + param ( + #[Parameter(Mandatory=$false)][Hashtable] $InputHashtable + #[Parameter(Mandatory=$false)][Switch] $DebugMode = $false + ) + + begin { + + Invoke-EmarsysLogin + + } + + process { + + #| Out-GridView -PassThru | Select -first 20 +# $fields | Out-GridView +# #$fields | Export-Csv -Path ".\fields.csv" -Encoding Default -NoTypeInformation -Delimiter "`t" +# #$fields | Select @{name="field_id";expression={ $_.id }}, @{name="fieldname";expression={$_.name}} -ExpandProperty choices | Export-Csv -Path ".\fields_choices.csv" -Encoding Default -NoTypeInformation -Delimiter "`t" + +# $c = Invoke-emarsys -cred $cred -uri "$( $settings.base )field/translate/de" -method Get + + $emarsys = $Script:variableCache.emarsys + $fields = [System.Collections.ArrayList]@(1,2,3,31) + + <# + $keys = [System.Collections.ArrayList]@(378808151,378808960) + $fetch = $emarsys.getContactData("id",$fields,$keys) + #> + + $keys = [System.Collections.ArrayList]@("florian.von.bracht@apteco.tld","florian.friedrichs@apteco.tld") + $fetch = $emarsys.getContactData("3",$fields,$keys) + + $fetch + + } + + end { + + } + +} + + diff --git a/AptecoPSFramework/plugins/emarsys/Public/emarsys/Get-FieldsTranslated.ps1 b/AptecoPSFramework/plugins/emarsys/Public/emarsys/Get-FieldsTranslated.ps1 new file mode 100644 index 0000000..81df73f --- /dev/null +++ b/AptecoPSFramework/plugins/emarsys/Public/emarsys/Get-FieldsTranslated.ps1 @@ -0,0 +1,46 @@ + + + + + +function Get-FieldsTranslated { + [CmdletBinding()] + param ( + [Parameter(Mandatory=$true)][String] $Language + #[Parameter(Mandatory=$false)][Switch] $DebugMode = $false + ) + + begin { + + Invoke-EmarsysLogin + + } + + process { + + #| Out-GridView -PassThru | Select -first 20 +# $fields | Out-GridView +# #$fields | Export-Csv -Path ".\fields.csv" -Encoding Default -NoTypeInformation -Delimiter "`t" +# #$fields | Select @{name="field_id";expression={ $_.id }}, @{name="fieldname";expression={$_.name}} -ExpandProperty choices | Export-Csv -Path ".\fields_choices.csv" -Encoding Default -NoTypeInformation -Delimiter "`t" + +# $c = Invoke-emarsys -cred $cred -uri "$( $settings.base )field/translate/de" -method Get + + # TODO remove this later + $stringSecure = ConvertTo-SecureString -String ( Convert-SecureToPlaintext $Script:settings.login.secret ) -AsPlainText -Force + $cred = [pscredential]::new( $Script:settings.login.username, $stringSecure ) + + #$emarsys = $Script:variableCache.emarsys + + $fields = Invoke-emarsys -cred $cred -uri "$( $Script:settings.base )field/translate/$( $Language )" -method "Get" + + $fields + + } + + end { + + } + +} + + diff --git a/AptecoPSFramework/plugins/emarsys/Public/emarsys/Get-ListContacts.ps1 b/AptecoPSFramework/plugins/emarsys/Public/emarsys/Get-ListContacts.ps1 new file mode 100644 index 0000000..59f4828 --- /dev/null +++ b/AptecoPSFramework/plugins/emarsys/Public/emarsys/Get-ListContacts.ps1 @@ -0,0 +1,42 @@ + + + + + +function Get-ListContacts { + [CmdletBinding()] + param ( + #[Parameter(Mandatory=$false)][Hashtable] $InputHashtable + #[Parameter(Mandatory=$false)][Switch] $DebugMode = $false + ) + + begin { + + Invoke-EmarsysLogin + + } + + process { + + #| Out-GridView -PassThru | Select -first 20 +# $fields | Out-GridView +# #$fields | Export-Csv -Path ".\fields.csv" -Encoding Default -NoTypeInformation -Delimiter "`t" +# #$fields | Select @{name="field_id";expression={ $_.id }}, @{name="fieldname";expression={$_.name}} -ExpandProperty choices | Export-Csv -Path ".\fields_choices.csv" -Encoding Default -NoTypeInformation -Delimiter "`t" + +# $c = Invoke-emarsys -cred $cred -uri "$( $settings.base )field/translate/de" -method Get + + $emarsys = $Script:variableCache.emarsys + + $fetch = $emarsys.fetchListContacts("31000652") + + $fetch + + } + + end { + + } + +} + + diff --git a/AptecoPSFramework/plugins/emarsys/Public/emarsys/Get-ListCount.ps1 b/AptecoPSFramework/plugins/emarsys/Public/emarsys/Get-ListCount.ps1 new file mode 100644 index 0000000..4e91bb9 --- /dev/null +++ b/AptecoPSFramework/plugins/emarsys/Public/emarsys/Get-ListCount.ps1 @@ -0,0 +1,42 @@ + + + + + +function Get-ListCount { + [CmdletBinding()] + param ( + #[Parameter(Mandatory=$false)][Hashtable] $InputHashtable + #[Parameter(Mandatory=$false)][Switch] $DebugMode = $false + ) + + begin { + + Invoke-EmarsysLogin + + } + + process { + + #| Out-GridView -PassThru | Select -first 20 +# $fields | Out-GridView +# #$fields | Export-Csv -Path ".\fields.csv" -Encoding Default -NoTypeInformation -Delimiter "`t" +# #$fields | Select @{name="field_id";expression={ $_.id }}, @{name="fieldname";expression={$_.name}} -ExpandProperty choices | Export-Csv -Path ".\fields_choices.csv" -Encoding Default -NoTypeInformation -Delimiter "`t" + +# $c = Invoke-emarsys -cred $cred -uri "$( $settings.base )field/translate/de" -method Get + + $emarsys = $Script:variableCache.emarsys + + $count = $emarsys.countList(31000652) + + $count + + } + + end { + + } + +} + + diff --git a/AptecoPSFramework/plugins/emarsys/Public/emarsys/Get-Mailings.ps1 b/AptecoPSFramework/plugins/emarsys/Public/emarsys/Get-Mailings.ps1 new file mode 100644 index 0000000..0fa234c --- /dev/null +++ b/AptecoPSFramework/plugins/emarsys/Public/emarsys/Get-Mailings.ps1 @@ -0,0 +1,28 @@ + + +function Get-Mailings { + [CmdletBinding()] + param ( + [Parameter(Mandatory=$false)][Hashtable] $InputHashtable + #[Parameter(Mandatory=$false)][Switch] $DebugMode = $false + ) + + begin { + + Invoke-EmarsysLogin + + } + + process { + + + + } + + end { + + } + +} + + diff --git a/AptecoPSFramework/plugins/emarsys/Public/emarsys/Remove-Contact.ps1 b/AptecoPSFramework/plugins/emarsys/Public/emarsys/Remove-Contact.ps1 new file mode 100644 index 0000000..4032a3d --- /dev/null +++ b/AptecoPSFramework/plugins/emarsys/Public/emarsys/Remove-Contact.ps1 @@ -0,0 +1,47 @@ + +function Remove-Contact { + [CmdletBinding()] + param ( + #[Parameter(Mandatory=$false)][Hashtable] $InputHashtable + #[Parameter(Mandatory=$false)][Switch] $DebugMode = $false + ) + + begin { + + Invoke-EmarsysLogin + + } + + process { + + #| Out-GridView -PassThru | Select -first 20 +# $fields | Out-GridView +# #$fields | Export-Csv -Path ".\fields.csv" -Encoding Default -NoTypeInformation -Delimiter "`t" +# #$fields | Select @{name="field_id";expression={ $_.id }}, @{name="fieldname";expression={$_.name}} -ExpandProperty choices | Export-Csv -Path ".\fields_choices.csv" -Encoding Default -NoTypeInformation -Delimiter "`t" + +# $c = Invoke-emarsys -cred $cred -uri "$( $settings.base )field/translate/de" -method Get + + $emarsys = $Script:variableCache.emarsys + + #$fields = $emarsys.getFields($false) + + $a = [System.Collections.ArrayList]@( + + "florian.von.bracht@apteco.tld" + "florian.friedrichs@apteco.tld" + + ) + + $res = $emarsys.deleteContact("3", $a) + + $res + + } + + end { + + } + +} + + diff --git a/AptecoPSFramework/plugins/emarsys/Public/emarsys/Remove-ContactFromList.ps1 b/AptecoPSFramework/plugins/emarsys/Public/emarsys/Remove-ContactFromList.ps1 new file mode 100644 index 0000000..0ab1e92 --- /dev/null +++ b/AptecoPSFramework/plugins/emarsys/Public/emarsys/Remove-ContactFromList.ps1 @@ -0,0 +1,48 @@ + +function Remove-ContactFromList { + [CmdletBinding()] + param ( + #[Parameter(Mandatory=$false)][Hashtable] $InputHashtable + [Parameter(Mandatory=$true)][Array] $Remove + ,[Parameter(Mandatory=$true)][Int] $ListId + ) + + begin { + + Invoke-EmarsysLogin + + } + + process { + + #| Out-GridView -PassThru | Select -first 20 +# $fields | Out-GridView +# #$fields | Export-Csv -Path ".\fields.csv" -Encoding Default -NoTypeInformation -Delimiter "`t" +# #$fields | Select @{name="field_id";expression={ $_.id }}, @{name="fieldname";expression={$_.name}} -ExpandProperty choices | Export-Csv -Path ".\fields_choices.csv" -Encoding Default -NoTypeInformation -Delimiter "`t" + +# $c = Invoke-emarsys -cred $cred -uri "$( $settings.base )field/translate/de" -method Get + + $emarsys = $Script:variableCache.emarsys + + #$fields = $emarsys.getFields($false) + + $a = [System.Collections.ArrayList]@() + $a.AddRange($Remove) + + #$res = $emarsys.deleteContactFromList("3", 31000652, $a) + $res = $emarsys.deleteContactFromList("3", $ListId, $a) + + + #$res = $emarsys.deleteContactFromList("id", 31000652, $a) + + $res + + } + + end { + + } + +} + + diff --git a/AptecoPSFramework/plugins/emarsys/Public/emarsys/get-fields.ps1 b/AptecoPSFramework/plugins/emarsys/Public/emarsys/get-fields.ps1 new file mode 100644 index 0000000..96c4e3d --- /dev/null +++ b/AptecoPSFramework/plugins/emarsys/Public/emarsys/get-fields.ps1 @@ -0,0 +1,42 @@ + + + + + +function Get-Fields { + [CmdletBinding()] + param ( + #[Parameter(Mandatory=$false)][Hashtable] $InputHashtable + #[Parameter(Mandatory=$false)][Switch] $DebugMode = $false + ) + + begin { + + Invoke-EmarsysLogin + + } + + process { + + #| Out-GridView -PassThru | Select -first 20 +# $fields | Out-GridView +# #$fields | Export-Csv -Path ".\fields.csv" -Encoding Default -NoTypeInformation -Delimiter "`t" +# #$fields | Select @{name="field_id";expression={ $_.id }}, @{name="fieldname";expression={$_.name}} -ExpandProperty choices | Export-Csv -Path ".\fields_choices.csv" -Encoding Default -NoTypeInformation -Delimiter "`t" + +# $c = Invoke-emarsys -cred $cred -uri "$( $settings.base )field/translate/de" -method Get + + $emarsys = $Script:variableCache.emarsys + + $fields = $emarsys.getFields($false) + + $fields + + } + + end { + + } + +} + + diff --git a/AptecoPSFramework/plugins/emarsys/Public/peoplestage/get-messages.ps1 b/AptecoPSFramework/plugins/emarsys/Public/peoplestage/get-messages.ps1 new file mode 100644 index 0000000..75ca4f8 --- /dev/null +++ b/AptecoPSFramework/plugins/emarsys/Public/peoplestage/get-messages.ps1 @@ -0,0 +1,202 @@ + + +function Get-Messages { + [CmdletBinding()] + param ( + [Parameter(Mandatory=$false)][Hashtable] $InputHashtable + #[Parameter(Mandatory=$false)][Switch] $DebugMode = $false + ) + + begin { + + + #----------------------------------------------- + # LOG + #----------------------------------------------- + + $moduleName = "GETMESSAGES" + + # Start the log + Write-Log -message $Script:logDivider + Write-Log -message $moduleName -Severity INFO + + # Log the params, if existing + Write-Log -message "INPUT:" + if ( $InputHashtable ) { + $InputHashtable.Keys | ForEach-Object { + $param = $_ + Write-Log -message " $( $param ) = '$( $InputHashtable[$param] )'" -writeToHostToo $false + } + } + + #----------------------------------------------- + # DEPENDENCIES + #----------------------------------------------- + + #Import-Module MeasureRows + #Import-Module SqlServer + #Import-Module ConvertUnixTimestamp + #Import-Lib -IgnorePackageStructure + + # Load SQLite library + Add-Type -Path "$( $Script:pluginRoot )\lib\SQLite\System.Data.SQLite.dll" + + #Load Up PostGre libraries + Add-Type -Path "$( $Script:pluginRoot )\lib\PostGre\Npgsql.dll" + Add-Type -Path "$( $Script:pluginRoot )\lib\PostGre\Npgsql.NetTopologySuite.dll" + + } + + process { + + $mailings = [System.Collections.ArrayList]@() + $mailings.AddRange($Script:settings.messageOptions) + + <# + [void]$mailings.Add( + [PSCustomObject]@{ + "id" = "a" + "name" = "add" + } + ) + [void]$mailings.Add( + [PSCustomObject]@{ + "id" = "r" + "name" = "remove" + } + ) + #> + + # Load and filter list into array of mailings objects + $mailingsList = [System.Collections.ArrayList]@() + $mailings | ForEach-Object { + $mailing = $_ + [void]$mailingsList.add( + [Mailing]@{ + mailingId=$mailing.id + mailingName=$mailing.name + } + ) + } + + # Transform the mailings array into the needed output format + $columns = @( + @{ + name="id" + expression={ $_.mailingId } + } + @{ + name="name" + expression={ $_.toString() } + } + ) + + $messages = [System.Collections.ArrayList]@() + [void]$messages.AddRange(@( $mailingsList | Select-Object $columns )) + + If ( $messages.count -gt 0 ) { + + Write-Log "Loaded $( $messages.Count ) messages" -severity INFO #-WriteToHostToo $false + + } else { + + $msg = "No messages loaded -> please check!" + Write-Log -Message $msg -Severity ERROR + throw [System.IO.InvalidDataException] $msg + + } + + # Return + $messages + + } + + end { + + } + +} + + + +<# +#----------------------------------------------- + # INITIATING A LOCAL DATABASE + #----------------------------------------------- + + # Decide wether to use a local one or :memory: + $tempDB = New-TemporaryFile # :memory: + + # Create the connection + #$connString = "Data Source=""$( $sqliteFile )"";Version=3;New=$( $new );Read Only=$( $readonly );$( $additionalParameters )" + $additionalParameters = "Journal Mode=MEMORY;Cache Size=-4000;Page Size=4096;" + $dbConnectionString = "Data Source=""$( $tempDB )"";$( $additionalParameters )" + $dbConnection = [System.Data.SQLite.SQLiteConnection]::new($dbConnectionString) + + + #----------------------------------------------- + # OPEN THE DATABASE + #----------------------------------------------- + + $retries = 10 + $retrycount = 0 + $secondsDelay = 2 + $completed = $false + + while (-not $completed) { + try { + $dbConnection.open() + Write-Host -message "Connection succeeded." + $completed = $true + } catch [System.Management.Automation.MethodInvocationException] { + if ($retrycount -ge $retries) { + Write-Host -message "Connection failed the maximum number of $( $retries ) times." -severity ([LogSeverity]::ERROR) + throw $_ + exit 0 + } else { + Write-Host -message "Connection failed $( $retrycount ) times. Retrying in $( $secondsDelay ) seconds." -severity ([LogSeverity]::WARNING) + Start-Sleep -Seconds $secondsDelay + $retrycount++ + } + } + } + + + } + + #> + + <# +# Open the database connection +$connString = $settings.globalDB #"Host=myserver;Username=mylogin;Password=mypass;Database=mydatabase"; +$conn = [Npgsql.NpgsqlConnection]::new($connString ) +[void]$conn.OpenAsync() + + +$tries = 0 +$maxTries = 10 +Do { + If ($tries -gt 0 ) { + Start-Sleep -Milliseconds 500 + Write-Host "Another try" + } + $tries += 1 +} Until ( $conn.State -eq "Open" -or $tries -eq $maxTries) + +If ( $conn.State -ne "Open" ) { + # TODO [ ] ERROR + Write-Warning "Connection not opened" +} + +$cmd = $conn.CreateCommand() +$cmd.CommandText = "SELECT * FROM information_schema.tables WHERE table_schema = 'public' ORDER BY table_name;" + +# load data +$datatable = [System.Data.DataTable]::new() +$sqlResult = $cmd.ExecuteReader() +$datatable.Load($sqlResult, [System.Data.Loadoption]::Upsert) +$sqlResult.close() +$cmd.Dispose() + +$conn.Close() +#> \ No newline at end of file diff --git a/AptecoPSFramework/plugins/emarsys/README.md b/AptecoPSFramework/plugins/emarsys/README.md new file mode 100644 index 0000000..591e819 --- /dev/null +++ b/AptecoPSFramework/plugins/emarsys/README.md @@ -0,0 +1,54 @@ + + +# Quickstart + + +```PowerShell + +Start-Process "powershell.exe" -WorkingDirectory "C:\faststats\scripts\channels\emarsys" +#Set-Location -Path "C:\faststats\scripts\channels\emarsys" + +# Import the module +Import-Module aptecopsframework -Verbose + +Add-PluginFolder "C:\faststats\scripts\AptecoPSPlugins" + +# Choose a plugin +$plugin = get-plugins | Select guid, name, version, update, path | Out-GridView -PassThru | Select -first 1 + +# Install the plugin before loading it (installing dependencies) +#Install-Plugin -Guid $plugin.guid + + +# Import the plugin into this session +import-plugin -Guid $plugin.guid + +# Get merged settings for this plugin and change some +$settings = Get-settings +$settings.logfile = ".\file.log" +$settings.login.username = "apt12345" +$settings.login.secret = Convert-PlaintextToSecure -String "12345zdsafjhgas" + +# Set the settings +Set-Settings -PSCustom $settings + +# Save the settings into a file +$settingsFile = ".\settings.yaml" +Export-Settings -Path $settingsFile + +``` + +# Functions + + +```PowerShell + +# Import the module +Import-Module aptecopsframework -Verbose +Import-Settings -Path "C:\faststats\scripts\channels\emarsys\settings.yaml" + +# List all commands of this plugin +get-command -module "*emarsys*" + + +``` \ No newline at end of file diff --git a/AptecoPSFramework/plugins/emarsys/notes.ps1 b/AptecoPSFramework/plugins/emarsys/notes.ps1 new file mode 100644 index 0000000..c9bbe27 --- /dev/null +++ b/AptecoPSFramework/plugins/emarsys/notes.ps1 @@ -0,0 +1,20 @@ +<# +$stringSecure = ConvertTo-SecureString -String ( Get-SecureToPlaintext $settings.login.secret ) -AsPlainText -Force + $cred = [pscredential]::new( $settings.login.username, $stringSecure ) + + # Read static attribute + [Emarsys]::allowNewFieldCreation + + # Create emarsys object + $emarsys = [Emarsys]::new($cred,$settings.base) + + [uint64]$currentTimestamp = Get-Unixtime -inMilliseconds -timestamp $timestamp + + + Created a list with Add-ContactList and got id 31000652 on 2024-03-25 + + Get-ListCount + + Zweite Liste: 890495209 + +#> \ No newline at end of file diff --git a/AptecoPSFramework/plugins/emarsys/settings/defaultsettings.ps1 b/AptecoPSFramework/plugins/emarsys/settings/defaultsettings.ps1 new file mode 100644 index 0000000..622968e --- /dev/null +++ b/AptecoPSFramework/plugins/emarsys/settings/defaultsettings.ps1 @@ -0,0 +1,76 @@ +[PSCustomObject]@{ + + # General + "providername" = "PSemarsys" + + # API + "base" = "https://api.emarsys.net/api/v2/" # main url to use for cleverreach, could be changed for newer versions or using API gateways + "contentType" = "application/json; charset=utf-8" # content type string that is always used for API requests + "pageSize" = 500 # if paging is used for the API requests, this is the default setting for a pagesize + #"mailingLimit" = 999 + #"additionalHeaders" = [PSCustomObject]@{ + #"X-API" = "abcdef" + #} # static headers that should be send to the URL, sometimes needed for API gateways + #"additionalParameters" = [PSCustomObject]@{ + #"Proxy" = "http://proxy.example.com" + #"SkipHeaderValidation" = $true + #} # additional parameter for the Invoke-RestMethod call like Proxy or ProxyCredential, see https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/invoke-restmethod + #"logAPIrequests" = $true # log information like 'GET https://rest.cleverreach.com:443/v3/groups.json/1158984/stats' + + # Error handling + "errorhandling" = [PSCustomObject]@{ + + # Delay, if a problem happens and will be repeated + "HttpErrorDelay" = 200 + + # Specific http errors and their settings + "RepeatOnHttpErrors" = [Array]@(502) + "MaximumRetriesOnHttpErrorList" = 3 + + # Generic errors like 404 that are not on the specific list + "MaximumRetriesGeneric" = 1 + + } + + # API Authentication + "login" = [PSCustomObject]@{ + "username" = "" + "secret" = "" + } + + # Get messages options + "messageOptions" = @( + # [PSCustomObject]@{ + # "id" = "add" + # "name" = "Add coupons" + # } + <# + [PSCustomObject]@{ + "id" = "r" + "name" = "remove" + } + #> + ) + + + # Upload settings + "upload" = [PSCustomObject]@{ + "countRowsInputFile" = $true + "createNewFields" = $true + "maximumThreads" = 100 # At emarsys it is more efficient to use multiple threads + } + + # Broadcast settings + "broadcast" = [PSCustomObject]@{ + + } + + "preview" = [PSCustomObject]@{ + } + + "responses" = [PSCustomObject]@{ + + } + +} +