diff --git a/.gitignore b/.gitignore index 235636f..970e6b8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ # Ignore log files +log *.log # Ignore temporary directory @@ -6,4 +7,6 @@ tmp # Ignore custom configuration files custom.ini -registry.info \ No newline at end of file +*.token +*.key +res/* \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index ce2a7d4..f0ccad7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,39 @@ # Changelog -All notable changes to the [Alteryx deploy](https://github.com/Akaizoku/alteryx-deploy) project will be documented in this file. +All notable changes to the [`alteryx-deploy`](https://github.com/Akaizoku/alteryx-deploy) utility will be documented in this file. Roadmap and backlog are documented in the corresponding [GitHub project](https://github.com/users/Akaizoku/projects/4). The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.0.0](https://github.com/Akaizoku/alteryx-deploy/releases/2.0.0) - 2024-10-08 + +Complete revamp to provide support for new installers (2022.3+), license portal API, as well as guardrails and usefull error handling. + +### Added + +- Invoke-DownloadAlteryx: Fetch releases from the Alteryx license portal +- Invoke-PatchAlteryx: Install patch upgrades +- Invoke-PingAlteryx: Check Server UI (Gallery) connectivity +- Invoke-RollbackAlteryx: Rollback to previous known stable state +- Invoke-SetupScript: Script configuration wizard +- Open-Alteryx: Open Alteryx (G)UI +- Repair-Alteryx: Repair embedded MongoDB database +- Set-AlteryxConfiguration: Configure Alteryx system settings +- Show-Help: Display script help documentation + +### Changed + +- Invoke-ActivateAlteryx: Improve process robustness +- Invoke-BackupAlteryx: Improve process robustness +- Invoke-DeactivateAlteryx: Deactivate licenses one-by-one +- Invoke-RestartAlteryx: Improve process robustness +- Invoke-RestoreAlteryx: Redesign process to improve robustness +- Invoke-StartAlteryx: Improve process robustness +- Invoke-StopAlteryx: Improve process robustness +- Uninstall-Alteryx: Improve process robustness +- Update-Alteryx: Add rollback in case of failure +- Various changes were made to configuration files, including encryption of sensitive information; Please use the `setup` command to configure the scripts. + ## [1.1.2](https://github.com/Akaizoku/alteryx-deploy/releases/1.1.2) - 2021-12-13 UX improvements diff --git a/Deploy-Alteryx.ps1 b/Deploy-Alteryx.ps1 index 4b66d64..415985f 100644 --- a/Deploy-Alteryx.ps1 +++ b/Deploy-Alteryx.ps1 @@ -6,36 +6,144 @@ Deploy Alteryx .DESCRIPTION - Deploy and configure Alteryx + Deploy and configure Alteryx on the current machine .PARAMETER Action The action parameter corresponds to the operation to perform. - Eleven options are available: + Multiple options are available: - activate: activate the Alteryx application license - backup: backup the Alteryx application database - configure: configure the Alteryx application - deactivate: deactivate the Alteryx application license + - download: download latest Alteryx application release + - help: display the help documentation - install: install the Alteryx application - - repair: repair the Alteryx application database + - open: open the Alteryx application - patch: patch upgrade the Alteryx application + - ping: check the status of the Alteryx application + - repair: repair the Alteryx application database - restart: restart the Alteryx application - restore: restore a backup of the Alteryx application database + - rollback: restore a previous known state of the Alteryx application + - setup: set-up the script configuration - show: display the script configuration - start: start the Alteryx application - stop: stop the Alteryx application - uninstall: uninstall the Alteryx application - upgrade: upgrade the Alteryx application + .PARAMETER Version + The optional version parameter enables users to speficy a version at runtime to override the script configuration. + + .PARAMETER BackupPath + The optional back-up path paramater enables users to specify a back-up file location at runtime to override the script configuration. + + .PARAMETER Product + The optional product parameter enabels users to specify the product to manage. It defaults to Server. + + .PARAMETER LicenseKey + The optional license key paramater enables users to specify one or more license keys at runtime to override the script configuration. + .PARAMETER Unattended The unattended switch define if the script should run in silent mode without any user interaction. + .EXAMPLE + .\Deploy-Alteryx.ps1 -Action "setup" + + Start the configuration wizard to guide the user through the set-up of the alteryx-deploy script. + + .EXAMPLE + .\Deploy-Alteryx.ps1 -Action "show" + + Display the current script configuration back to the user. + + .EXAMPLE + .\Deploy-Alteryx.ps1 -Action "download" + + Download the latest version of the licensed Alteryx application from the Alteryx license portal. + + .EXAMPLE + .\Deploy-Alteryx.ps1 -Action "install" + + Start the installation process of the Alteryx application and its add-ons if configured. + + .EXAMPLE + .\Deploy-Alteryx.ps1 -Action "upgrade" + + Start the (major) upgrade process of the Alteryx application and its add-ons if configured. + + .EXAMPLE + .\Deploy-Alteryx.ps1 -Action "patch" + + Start the patch process of the Alteryx application. + + .EXAMPLE + .\Deploy-Alteryx.ps1 -Action "uninstall" + + Start the uninstallation process of the Alteryx application and all of its add-ons. + + .EXAMPLE + .\Deploy-Alteryx.ps1 -Action "activate" + + License the Alteryx application by registering the specified license keys through the Alteryx licensing system. + + .EXAMPLE + .\Deploy-Alteryx.ps1 -Action "deactivate" + + Deregister the specified license keys through the Alteryx licensing system. + + .EXAMPLE + .\Deploy-Alteryx.ps1 -Action "backup" + + Start the back-up process of the Alteryx database and all of the configuration files of the application. + + .EXAMPLE + .\Deploy-Alteryx.ps1 -Action "restore" + + Start the restoration process of the Alteryx database and all of the configuration files of the application from a back-up file. + + .EXAMPLE + .\Deploy-Alteryx.ps1 -Action "repair" + + Start the repair process of the Alteryx database. + + .EXAMPLE + .\Deploy-Alteryx.ps1 -Action "rollback" + + Start the rollback process of the Alteryx application back to a previous known state from a back-up file. + + .EXAMPLE + .\Deploy-Alteryx.ps1 -Action "start" + + Start the service powering the Alteryx application. + + .EXAMPLE + .\Deploy-Alteryx.ps1 -Action "stop" + + Stop the service powering the Alteryx application. + + .EXAMPLE + .\Deploy-Alteryx.ps1 -Action "restart" + + Restart the service powering the Alteryx application. + + .EXAMPLE + .\Deploy-Alteryx.ps1 -Action "ping" + + Check the status of the service powering the Alteryx application and the connectivity to the Gallery. + + .EXAMPLE + .\Deploy-Alteryx.ps1 -Action "open" + + Open the user interface of the Alteryx applciation. + .NOTES File name: Deploy-Alteryx.ps1 Author: Florian Carrier Creation date: 2021-06-13 - Last modified: 2022-04-27 + Last modified: 2024-09-23 Dependencies: - PowerShell Tool Kit (PSTK) - Alteryx PowerShell Module (PSAYX) @@ -63,15 +171,19 @@ Param ( [ValidateSet ( "activate", "backup", - "deactivate", - "install", - "repair", "configure", "deactivate", + "download", + "help", "install", + "open", "patch", + "ping", + "repair", "restart", "restore", + "rollback", + "setup", "show", "start", "stop", @@ -140,25 +252,27 @@ Begin { # General $ISOTimeStamp = Get-Date -Format "yyyyMMdd_HHmmss" - # Configuration - $LibDirectory = Join-Path -Path $PSScriptRoot -ChildPath "lib" - $ConfDirectory = Join-Path -Path $PSScriptRoot -ChildPath "conf" - $DefaultProperties = "default.ini" - $CustomProperties = "custom.ini" + # Script configuration + $ScriptProperties = [Ordered]@{ + LibDirectory = (Join-Path -Path $PSScriptRoot -ChildPath "lib") + ConfDirectory = (Join-Path -Path $PSScriptRoot -ChildPath "conf") + DefaultProperties = "default.ini" + CustomProperties = "custom.ini" + } # ---------------------------------------------------------------------------- # * Modules # ---------------------------------------------------------------------------- # Dependencies $Modules = [Ordered]@{ - "PSTK" = "1.2.4" - "PSAYX" = "1.0.1" + "PSTK" = "1.2.6" + "PSAYX" = "1.1.1" } # Load modules foreach ($Module in $Modules.GetEnumerator()) { try { # Check if package is available locally - Import-Module -Name (Join-Path -Path $LibDirectory -ChildPath $Module.Name) -MinimumVersion $Module.Value -ErrorAction "Stop" -Force + Import-Module -Name (Join-Path -Path $ScriptProperties.LibDirectory -ChildPath $Module.Name) -MinimumVersion $Module.Value -ErrorAction "Stop" -Force $ModuleVersion = (Get-Module -Name $Module.Name).Version Write-Log -Type "CHECK" -Object "The $($Module.Name) module (v$ModuleVersion) was successfully loaded from the library directory." } catch { @@ -168,7 +282,7 @@ Begin { $ModuleVersion = (Get-Module -Name $Module.Name).Version Write-Log -Type "CHECK" -Object "The $($Module.Name) module (v$ModuleVersion) was successfully loaded." } catch { - Throw "The $($Module.Name) module (v$($Module.Value)) could not be loaded. Make sure it has been installed on the machine or packaged in the ""$LibDirectory"" directory" + Throw "The $($Module.Name) module (v$($Module.Value)) could not be loaded. Make sure it has been installed on the machine or packaged in the ""$($ScriptProperties.LibDirectory)"" directory" } } } @@ -177,7 +291,7 @@ Begin { # * Script configuration # ---------------------------------------------------------------------------- # General settings - $Properties = Get-Properties -File $DefaultProperties -Directory $ConfDirectory -Custom $CustomProperties + $Properties = Get-Properties -File $ScriptProperties.DefaultProperties -Directory $ScriptProperties.ConfDirectory -Custom $ScriptProperties.CustomProperties # Resolve relative paths Write-Log -Type "DEBUG" -Message "Script structure check" $Properties = Get-Path -PathToResolve $Properties.RelativePaths -Hashtable $Properties -Root $PSScriptRoot @@ -195,7 +309,8 @@ Begin { # ------------------------------------------------------------------------------ # Ensure shell is running as 64 bit process if ([Environment]::Is64BitProcess -eq $false) { - Write-Log -Type "ERROR" -Message "PowerShell is running as a 32-bit process" -ExitCode 1 + Write-Log -Type "ERROR" -Message "PowerShell is running as a 32-bit process" + Write-Log -Type "INFO" -Message "Please run PowerShell as a 64-bit process" -ExitCode 1 } # ---------------------------------------------------------------------------- @@ -219,6 +334,25 @@ Begin { Sync-EnvironmentVariable -Name $EnvironmentVariable -Scope $Properties.EnvironmentVariableScope | Out-Null } + # Check installation path + if ($Properties.InstallationPath -eq "") { + if ($Unattended -eq $false) { + do { + Write-Log -Type "WARN" -Message "Path not found $($Properties.InstallationPath)" + $Properties.InstallationPath = Read-Host -Prompt "Please enter the Alteryx installation path" + } until (Test-Object -Path $Properties.InstallationPath) + } else { + if ($Action -ne "install") { + # Retrieve path from registry + $Properties.InstallationPath = Get-AlteryxInstallDirectory + } else { + Write-Log -Type "ERROR" -Message "No Alteryx installation path has been provided" -ExitCode 1 + } + } + } elseif (Test-Object -Path $Properties.InstallationPath -NotFound) { + New-Item -Path $Properties.InstallationPath -ItemType "Directory" -Force | Out-Null + } + # ---------------------------------------------------------------------------- # * Options # ---------------------------------------------------------------------------- @@ -228,9 +362,9 @@ Begin { "PredictiveTools" "IntelligenceSuite" "DataPackages" - ) + ) $InstallationProperties = Get-Properties -File $Properties.InstallationOptions -Directory $Properties.ConfDirectory -ValidateSet $ValidateSet - $InstallationProperties.Add("Product", $Product) + $Properties.Add("Product", $Product) # Optional parameters if ($PSBoundParameters.ContainsKey("Version")) { $Properties.Version = $Version @@ -249,25 +383,56 @@ Begin { Process { # Check operation to perform switch ($Action) { - "activate" { Invoke-ActivateAlteryx -Properties $Properties -Unattended:$Unattended } - "backup" { Invoke-BackupAlteryx -Properties $Properties -Unattended:$Unattended } - "configure" { Set-Configuration -Properties $Properties -Unattended:$Unattended } - "deactivate" { Invoke-DeactivateAlteryx -Properties $Properties -Unattended:$Unattended } - "install" { Install-Alteryx -Properties $Properties -InstallationProperties $InstallationProperties -Unattended:$Unattended } - "repair" { Repair-Alteryx -Properties $Properties -Unattended:$Unattended } - "patch" { Invoke-PatchAlteryx -Properties $Properties -Unattended:$Unattended } - "restart" { Invoke-RestartAlteryx -Properties $Properties -Unattended:$Unattended } - "restore" { Invoke-RestoreAlteryx -Properties $Properties -Unattended:$Unattended } - "show" { Show-Configuration -Properties $Properties -InstallationProperties $InstallationProperties } - "start" { Invoke-StartAlteryx -Properties $Properties -Unattended:$Unattended } - "stop" { Invoke-StopAlteryx -Properties $Properties -Unattended:$Unattended } - "uninstall" { Uninstall-Alteryx -Properties $Properties -InstallationProperties $InstallationProperties -Unattended:$Unattended } - "upgrade" { Update-Alteryx -Properties $Properties -InstallationProperties $InstallationProperties -Unattended:$Unattended } - default { Write-Log -Type "ERROR" -Message """$Action"" operation is not supported" -ExitCode 1 } + "activate" { $Process = Invoke-ActivateAlteryx -Properties $Properties -Unattended:$Unattended } + "backup" { $Process = Invoke-BackupAlteryx -Properties $Properties -Unattended:$Unattended } + "configure" { $Process = Set-AlteryxConfiguration -Properties $Properties -Unattended:$Unattended } + "deactivate" { $Process = Invoke-DeactivateAlteryx -Properties $Properties -Unattended:$Unattended } + "download" { $Process = Invoke-DownloadAlteryx -Properties $Properties -InstallationProperties $InstallationProperties -Unattended:$Unattended } + "help" { $Process = Show-Help } + "install" { $Process = Install-Alteryx -Properties $Properties -InstallationProperties $InstallationProperties -Unattended:$Unattended } + "repair" { $Process = Repair-Alteryx -Properties $Properties -Unattended:$Unattended } + "open" { $Process = Open-Alteryx -Properties $Properties -Unattended:$Unattended } + "patch" { $Process = Invoke-PatchAlteryx -Properties $Properties -Unattended:$Unattended } + "ping" { $Process = Invoke-PingAlteryx -Properties $Properties -Unattended:$Unattended } + "repair" { $Process = Repair-Alteryx -Properties $Properties -Unattended:$Unattended } + "restart" { $Process = Invoke-RestartAlteryx -Properties $Properties -Unattended:$Unattended } + "restore" { $Process = Invoke-RestoreAlteryx -Properties $Properties -Unattended:$Unattended } + "rollback" { $Process = Invoke-RollbackAlteryx -Properties $Properties -InstallationProperties $InstallationProperties -Unattended:$Unattended } + "setup" { $Process = Invoke-SetupScript -Properties $Properties -ScriptProperties $ScriptProperties } + "show" { $Process = Show-Configuration -Properties $Properties -InstallationProperties $InstallationProperties } + "start" { $Process = Invoke-StartAlteryx -Properties $Properties -Unattended:$Unattended } + "stop" { $Process = Invoke-StopAlteryx -Properties $Properties -Unattended:$Unattended } + "uninstall" { $Process = Uninstall-Alteryx -Properties $Properties -InstallationProperties $InstallationProperties -Unattended:$Unattended } + "upgrade" { $Process = Update-Alteryx -Properties $Properties -InstallationProperties $InstallationProperties -Unattended:$Unattended } + default { Write-Log -Type "ERROR" -Message """$Action"" operation is not supported" -ExitCode 1 } } } End { - # Stop script and transcript - Stop-Script -ExitCode 0 + # Check outcome and gracefully end script + $Process = $Process[0] + if ($Process.ErrorCount -gt 0) { + if ($Process.ErrorCount -gt 1) { + $Errors = "with $($Process.ErrorCount) errors" + } else { + $Errors = "with $($Process.ErrorCount) error" + } + } else { + $Errors = "" + } + if ($Process.Status -eq "Completed") { + if ($Process.Success) { + Write-Log -Type "CHECK" -Message "Alteryx $Action process completed successfully" -ExitCode $Process.ExitCode + } else { + Write-Log -Type "WARN" -Message "Alteryx $Action process completed $Errors" -ExitCode $Process.ExitCode + } + } else { + switch ($Process.Status) { + "Cancelled" { $Outcome = "was cancelled" } + "Failed" { $Outcome = "failed" } + "Stopped" { $Outcome = "was stopped" } + default { $Outcome = "failed" } + } + Write-Log -Type "ERROR" -Message "Alteryx $Action process $Outcome $Errors" -ExitCode $Process.ExitCode + } } \ No newline at end of file diff --git a/LICENSE b/LICENSE index a076baf..e5e82ff 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021 Florian CARRIER +Copyright (c) 2021-2024 Florian CARRIER Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index d3f3a52..08a42cd 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,30 @@ -# Alteryx Deploy +# alteryx-deploy [![PSScriptAnalyzer](https://github.com/Akaizoku/alteryx-deploy/actions/workflows/scan.yml/badge.svg?branch=main)](https://github.com/Akaizoku/alteryx-deploy/actions/workflows/scan.yml) + + `alteryx-deploy` is a small PowerShell utility for the automation of the deployment and maintenance of Alteryx. -## Table of contents +## Table of Contents 1. [Usage](#usage) + 1. [Installation](#installation) + 2. [Configuration](#configuration) + 3. [Execution](#execution) 2. [Pre-requisites](#pre-requisites) 1. [Permissions](#permissions) 2. [PowerShell version](#powershell-version) 3. [PowerShell Modules](#powershell-modules) 4. [Alteryx](#alteryx) -3. [Configuration](#configuration) - 1. [Script configuration](#script-configuration) - 2. [Installation configuration](#installation-configuration) -4. [Parameters](#parameters) +3. [Parameters](#parameters) 1. [Mandatory](#mandatory) 1. [Action](#action) 2. [Optional](#optional) @@ -26,41 +35,89 @@ 5. [Unattended](#unattended) 6. [WhatIf](#whatif) 7. [Debug](#debug) -5. [Process](#process) - 1. [Installation](#installation) - 2. [Upgrade](#upgrade) - 3. [Uninstallation](#uninstallation) - 4. [Activation](#activation) - 5. [Deactivation](#deactivation) - 6. [Backup](#backup) - 7. [Restore](#restore) - 8. [Start](#start) - 9. [Stop](#stop) - 10. [Restart](#restart) -6. [Logs](#logs) -7. [Dependencies](#dependencies) -8. [Compatibility](#compatibility) -9. [Known issues](#known-issues) +4. [Process](#process) + 1. [Help](#help) + 2. [Set-up](#set-up) + 3. [Show](#show) + 4. [Download](#download) + 5. [Installation](#installation-1) + 6. [Upgrade](#upgrade) + 7. [Patch](#patch) + 8. [Uninstallation](#uninstallation) + 9. [Activation](#activation) + 10. [Deactivation](#deactivation) + 11. [Backup](#backup) + 12. [Restore](#restore) + 13. [Repair](#repair) + 14. [Rollback](#rollback) + 15. [Start](#start) + 16. [Stop](#stop) + 17. [Restart](#restart) + 18. [Ping](#ping) + 19. [Open](#open) +5. [Logs](#logs) +6. [Dependencies](#dependencies) +7. [Compatibility](#compatibility) +8. [Known issues](#known-issues) 1. [Access to the cloud file is denied](#access-to-the-cloud-file-is-denied) + 2. [Transcript is not stopped](#transcript-is-not-stopped) ## Usage -1. Check the `default.ini` configuration file located under the `conf` folder; -2. If needed, add custom configuration to the `custom.ini` configuration file in the same configuration folder; -3. Update the installation configuration in the file `install.ini`; -4. Run the `Deploy-Alteryx.ps1` script with the corresponding action parameter; - - activate: activate the Alteryx application license - - backup: backup the Alteryx application database - - deactivate: deactivate the Alteryx application license - - install: install the Alteryx application - - restart: restart the Alteryx application - - restore: restore a backup of the Alteryx application database - - show: display the script configuration - - start: start the Alteryx application - - stop: stop the Alteryx application - - uninstall: uninstall the Alteryx application - - upgrade: upgrade the Alteryx application -5. Check the logs. +### Installation + +Download the latest stable version from the [`alteryx-deploy`](https://github.com/Akaizoku/alteryx-deploy) GitHub repository. + +```powershell +curl --remote-name --remote-header-name "https://github.com/Akaizoku/alteryx-deploy/releases/download/1.1.2/alteryx-deploy.1.1.2.zip" +``` + +Alternatively, if you do not wish to install the PowerShell modules required as dependencies, you can download the portable version. + +```powershell +curl --remote-name --remote-header-name "https://github.com/Akaizoku/alteryx-deploy/releases/download/1.1.2/alteryx-deploy.1.1.2.portable.zip" +``` + +### Configuration + +A set-up wizard is available to interactively configure the script from the command prompt. + +```powershell +.\Deploy-Alteryx.ps1 -Action "setup" +``` + +### Execution + +1. Run the [`Deploy-Alteryx.ps1`](./Deploy-Alteryx.ps1) script with the corresponding action parameter; + - [activate](#activation): activate the Alteryx application license + - [backup](#backup): backup the Alteryx application database + - [configure](#configuration): configure the Alteryx application + - [deactivate](#deactivation): deactivate the Alteryx application license + - [download](#download): download latest Alteryx application release + - [help](#help) display the help documentation of the script + - [install](#installation): install the Alteryx application + - [open](#open): open the Alteryx application + - [patch](#patch): patch upgrade the Alteryx application + - [ping](#ping): check the status of the Alteryx application + - [repair](#repair): repair the Alteryx application database + - [restart](#restart): restart the Alteryx application + - [restore](#restore): restore a backup of the Alteryx application database + - [rollback](#rollback) restore a previous known state of the Alteryx application + - [setup](#set-up): set-up the script configuration + - [show](#show): display the script configuration + - [start](#start): start the Alteryx application + - [stop](#stop): stop the Alteryx application + - [uninstall](#uninstallation): uninstall the Alteryx application + - [upgrade](#upgrade): upgrade the Alteryx application +2. Check the logs. + +Example to display the current script configuration: + +```powershell +.\Deploy-Alteryx.ps1 -Action "show" +``` + +**Remark** It is strongly recommended to perform a test run using the [`-WhatIf`](#whatif) parameter if you are running the script or operation for the first time. ## Pre-requisites @@ -78,9 +135,16 @@ This script makes use of functions from the PowerShell Tool Kit ([PSTK]) module The modules must be [installed](https://docs.microsoft.com/en-us/powershell/module/powershellget/install-module) on the local machine, or placed in the `lib` folder at the root of the script directory. +```PowerShell +Install-Module -Name "PSTK" +Install-Module -Name "PSAYX" +``` + +Alternatively, do install the portable version of the script made available in the [`alteryx-deploy`](https://github.com/Akaizoku/alteryx-deploy) GitHub repository. + Example script structure with embedded dependencies: -```cmd +```bash .alteryx-deploy +---conf +---lib @@ -94,44 +158,10 @@ Example script structure with embedded dependencies: Alteryx installation files must be made available in the source directory (default `C:\Sources`). See the [compatibility section](#compatibility) for more information about the supported versions. -You can download them from . +You can download them from or download via the `download` action. Please refer to [Alteryx system requirements](https://help.alteryx.com/current/server/system-requirements) for minimum machine requirements. -## Configuration - -### Script configuration - -The default configuration of the utility is stored into `default.ini`. This file should not be amended. All custom configuration must be made in the `custom.ini` file. Any customisation done in that file will override the default values. - -Below is an example of custom configuration file: - -```ini -[Paths] -# Sources directory -SrcDirectory = D:\Alteryx\Sources -# Alteryx installation directory -InstallationPath = D:\Alteryx\Server -# Backup directory -BackupDirectory = D:\Alteryx\Server\backup -# Data packages installation directory -DataPackagesPath = D:\Alteryx\Data -``` - -### Installation configuration - -To configure which products should be installed, edit the `install.ini` configuration file located in the `conf` directory. - -Below is an example of installation configuration file: - -```ini -[Installation] -Server = true -PredictiveTools = true -IntelligenceSuite = true -DataPackages = false -``` - ## Parameters In addition to the configuration files presented above, parameters will define the operation performed during the execution. @@ -144,14 +174,24 @@ This section lists the mandatory parameters that must be specified to run the sc The `Action` parameter corresponds to the operation to perform. -Eleven options are available: +Nineteen options are available: - activate: activate the Alteryx application license - backup: backup the Alteryx application database +- configure: configure the Alteryx application - deactivate: deactivate the Alteryx application license +- download: download latest Alteryx application release +- help: display the help documentation - install: install the Alteryx application +- repair: repair the Alteryx application database +- open: open the Alteryx application +- patch: patch upgrade the Alteryx application +- ping: check the status of the Alteryx application +- repair: repair the Alteryx application database - restart: restart the Alteryx application - restore: restore a backup of the Alteryx application database +- rollback: restore a previous known state of the Alteryx application +- setup: set-up the script configuration - show: display the script configuration - start: start the Alteryx application - stop: stop the Alteryx application @@ -207,8 +247,63 @@ Below are the execution steps of the `.\Deploy-Alteryx.ps1` script. 1. The execution steps will vary depending on the configuration of the scripts. 2. The steps described below correspond to a complete and successfull execution of the script. +### Help + +Display the help documentation of the script. + +```powershell +Get-Help -Name "Deploy-Alteryx.ps1" -Full +``` + +### Set-up + +Start the configuration wizard to guide the user through the set-up of the alteryx-deploy script. + +Below are the steps to set-up the `alteryx-deploy` script configuration. + +```powershell +.\Deploy-Alteryx.ps1 -Action "setup" +``` + +1. Configure script parameters; +2. Configure license API token; +3. Configure Server API admin keys; +4. Configure installation properties; +5. Configure license key file + + +### Show + +Display the current script configuration back to the user. + +Below are the steps to display the `alteryx-deploy` script configuration. + +```powershell +.\Deploy-Alteryx.ps1 -Action "show" +``` + +### Download + +Download the latest version of the licensed Alteryx application from the Alteryx license portal. + +Below are the steps to download the Alteryx application. + +```powershell +.\Deploy-Alteryx.ps1 -Action "download" +``` + +1. Refresh license API portal access token; +2. Fetch information on latest release; +3. Download latest version of Alteryx Server (or Designer if specified with the `-Product` parameter); +4. Download Predictive Tools (if enabled); +5. Download Intelligence Suite (if enabled). + + ### Installation +Start the installation process of the Alteryx application and its add-ons if configured. + Below are the steps to install the Alteryx application. ```powershell @@ -218,10 +313,15 @@ Below are the steps to install the Alteryx application. 1. Install Alteryx Server (or Designer if specified with the `-Product` parameter); 2. Install Predictive Tools (if enabled); 3. Install Intelligence Suite (if enabled); -4. Install Data packages (if enabled). +4. Install Data packages (if enabled); +5. Activate licenses (if enabled). + ### Upgrade +Start the (major) upgrade process of the Alteryx application and its add-ons if configured. + Below are the steps to upgrade the Alteryx application. ```powershell @@ -235,8 +335,24 @@ Below are the steps to upgrade the Alteryx application. 5. Install Data packages (if enabled); 6. Check installation status and rollback if errors occurred. +### Patch + +Start the patch process of the Alteryx application. + +Below are the steps to patch the Alteryx application. + +```powershell +.\Deploy-Alteryx.ps1 -Action "patch" +``` + +1. Backup Alteryx database and configuration files; +2. Patch Alteryx Server (or Designer if specified with the `-Product` parameter); +3. Check installation status. + ### Uninstallation +Start the uninstallation process of the Alteryx application and all of its add-ons. + Below are the steps to uninstall the Alteryx application. ```powershell @@ -252,6 +368,8 @@ Below are the steps to uninstall the Alteryx application. ### Activation +License the Alteryx application by registering the specified license keys through the Alteryx licensing system. + Below are the steps to activate (license) the Alteryx application. ```powershell @@ -264,6 +382,8 @@ Below are the steps to activate (license) the Alteryx application. ### Deactivation +Deregister the specified license keys through the Alteryx licensing system. + Below are the steps to deactivate (license) the Alteryx application. ```powershell @@ -276,7 +396,9 @@ Below are the steps to deactivate (license) the Alteryx application. ### Backup -Below are the steps to backup the Alteryx application database. +Start the back-up process of the Alteryx database and all of the configuration files of the application. + +Below are the steps to back-up the Alteryx application database. ```powershell .\Deploy-Alteryx.ps1 -Action "backup" @@ -286,11 +408,13 @@ Below are the steps to backup the Alteryx application database. 2. Create database dump; 3. Create copy of application configuration files; 4. Backup controller token; -5. Compress all backup files; +5. Compress all back-up files; 6. Restart Alteryx Service (if it was running previously). ### Restore +Start the restoration process of the Alteryx database and all of the configuration files of the application from a back-up file. + Below are the steps to restore the Alteryx application database. ```powershell @@ -308,8 +432,40 @@ Below are the steps to restore the Alteryx application database. 7. Restore MongoDB database; 8. Restart Alteryx Service (if it was running previously). +### Repair + +Start the repair process of the Alteryx database. + +Below are the steps to repair the Alteryx application database. + +```powershell +.\Deploy-Alteryx.ps1 -Action "repair" +``` + +1. Rebuild MongoDB indexes. + + +**Remark**: No repair steps are available for version 2022.1 and above. + +### Rollback + +Start the rollback process of the Alteryx application back to a previous known state from a back-up file. + +Below are the steps to roll-back the Alteryx application. + +```powershell +.\Deploy-Alteryx.ps1 -Action "rollback" +``` + +1. Uninstall the current version; +2. Install previous version; +3. Restore database; +4. Restart service. + ### Start +Start the Alteryx service. + Below are the steps to start the Alteryx application. ```powershell @@ -322,6 +478,8 @@ Below are the steps to start the Alteryx application. ### Stop +Stop the service powering the Alteryx application. + Below are the steps to stop the Alteryx application. ```powershell @@ -334,6 +492,8 @@ Below are the steps to stop the Alteryx application. ### Restart +Restart the service powering the Alteryx application. + Below are the steps to restart the Alteryx application. ```powershell @@ -343,6 +503,32 @@ Below are the steps to restart the Alteryx application. 1. Stop Alteryx Service; 2. Start Alteryx Service. +### Ping + +Check the status of the service powering the Alteryx application and the connectivity to the Gallery. + +Below are the steps to ping the Alteryx application. + +```powershell +.\Deploy-Alteryx.ps1 -Action "ping" +``` + +1. Check that the Alteryx Service is running; +2. Check the HTTP status of the Gallery. + +### Open + +Open the user interface of the Alteryx applciation. + +Below are the steps to open the Alteryx application. + +```powershell +.\Deploy-Alteryx.ps1 -Action "open" +``` + +- Server: open the Gallery using the default web-browser; +- Designer: open the GUI. + ## Logs Transcript log files are generated in the log directory of the script. @@ -375,8 +561,8 @@ Below is an example of a successful installation log: This module depends on the usage of functions provided by two PowerShell modules: -1. PowerShell Tool Kit ([PSTK]) module (version 1.2.5) -2. Alteryx PowerShell ([PSAYX]) module (version 1.0.1) +1. PowerShell Tool Kit ([PSTK]) module (version 1.2.6); +2. Alteryx PowerShell ([PSAYX]) module (version 1.1.0). ## Compatibility @@ -390,6 +576,7 @@ Only the first version supported is listed. Later releases should also be compat | [1.1.0] | [2021.3] | 5.0 | 1.2.5 | 1.0.1 | | [1.1.1] | [2021.3] | 5.0 | 1.2.5 | 1.0.1 | | [1.1.2] | [2021.3] | 5.0 | 1.2.5 | 1.0.1 | +| [2.0.0] | [2024.1] | 5.0 | 1.2.6 | 1.1.1 | ## Known issues @@ -404,17 +591,24 @@ This error can occur during the backup of the application when [OneDrive](https: This has no impact on the backup process and only affects temporary files used to generate the backup archive. > 2021-11-21 02:12:55 INFO Remove staging backup folder -> > 2021-11-21 02:12:55 ERROR Access to the cloud file is denied It can be prevented by configuring a temporary directory (`TempDirectory`) that is not synchronised by OneDrive. Temporary files can also be removed manually. +### Transcript is not stopped + +It appears that in some environments, the transcript it not stopped as expected when exiting the script. + +If this occurs, simply run the command [`Stop-Transcript`](https://learn.microsoft.com/en-us/powershell/module/Microsoft.PowerShell.Host/Stop-Transcript) or [`Stop-AllTranscripts`](https://github.com/Akaizoku/PSTK/blob/main/Public/Stop-AllTranscripts.ps1). + [PSTK]: https://www.powershellgallery.com/packages/PSTK -[PSAYX]: https://www.powershellgallery.com/packages/PSAYX +[PSAYX]:https://www.powershellgallery.com/packages/PSAYX [1.0.0]:https://github.com/Akaizoku/alteryx-deploy/releases/1.0.0 [1.1.0]:https://github.com/Akaizoku/alteryx-deploy/releases/1.1.0 [1.1.1]:https://github.com/Akaizoku/alteryx-deploy/releases/1.1.1 [1.1.2]:https://github.com/Akaizoku/alteryx-deploy/releases/1.1.2 +[2.0.0]:https://github.com/Akaizoku/alteryx-deploy/releases/2.0.0 [2021.3]:https://help.alteryx.com/release-notes/server/server-20213-release-notes +[2024.1]:https://help.alteryx.com/release-notes/en/release-notes/server-release-notes/server-2024-1-release-notes.html [whitelist.alteryx.com]:(whitelist.alteryx.com) diff --git a/conf/default.ini b/conf/default.ini index 248f9d3..1a90c19 100644 --- a/conf/default.ini +++ b/conf/default.ini @@ -20,8 +20,6 @@ SrcDirectory = C:\Sources InstallationPath = C:\Program Files\Alteryx # Backup directory BackupDirectory = C:\ProgramData\Alteryx\Backup -# Predictive Tools directory -RDirectory = RInstaller # Data packages installation directory DataPackagesPath = C:\Program Files\Alteryx\Data # 7zip application path (required for data packages) @@ -30,14 +28,20 @@ DataPackagesPath = C:\Program Files\Alteryx\Data [Filenames] # Installation properties InstallationOptions = install.ini +# License file +LicenseFile = license.key +# License API refresh token file +LicenseAPIFile = license.token +# Server admin API key & secret +ServerAdminAPI = server.key [Misc.] # Installation language Language = English # Version -Version = 2021.4.2.02731 -# SSL/TLS -EnableSSL = false +Version = 2024.1.1.136 +# Service name +ServiceName = AlteryxService # InstallAware log InstallAwareLog = false # Activate Alteryx during installation @@ -48,15 +52,25 @@ ActivateOnUpgrade = true [License] # Licensing system URL LicensingURL = whitelist.alteryx.com -# License file -LicenseFile = # License email LicenseEmail = +# License Account ID +LicenseAccountID = + +[SSL] +# SSL/TLS +EnableSSL = false +# Application ID +ApplicationID = {eea9431a-a3d4-4c9b-9f9a-b83916c11c67} [Ports] +# Gallery HTTP communication port HTTPPort = 80 +# Gallery HTTPS communication port HTTPSPort = 443 +# Embedded MongoDB database communication port MongoDBPort = 27018 [Checks] -RelativePaths = ConfDirectory, PSDirectory, TempDirectory, LogDirectory, ResDirectory +# Script internal structure +RelativePaths = ConfDirectory, PSDirectory, TempDirectory, LogDirectory, ResDirectory \ No newline at end of file diff --git a/conf/ssl.ini b/conf/ssl.ini new file mode 100644 index 0000000..e48846e --- /dev/null +++ b/conf/ssl.ini @@ -0,0 +1,19 @@ +# ------------------------------------------------------------------------------ +# SSL/TLS self-signed certificate attributes +# ------------------------------------------------------------------------------ + +# Organisation (O) +Organisation = +# Organizational Unit (OU) +# Country (C) +Country = +# State (S) +State = +# Locality (L) +Locality = +# Common Name (CN) +CommonName = +# Email address +EmailAddress = +# Certificate friendly name +FriendlyName = Alteryx Server \ No newline at end of file diff --git a/powershell/Install-Alteryx.ps1 b/powershell/Install-Alteryx.ps1 index 3e63892..39aea77 100644 --- a/powershell/Install-Alteryx.ps1 +++ b/powershell/Install-Alteryx.ps1 @@ -16,13 +16,13 @@ function Install-Alteryx { File name: Install-Alteryx.ps1 Author: Florian Carrier Creation date: 2021-07-05 - Last modified: 2022-04-19 + Last modified: 2024-09-20 .LINK https://www.powershellgallery.com/packages/PSAYX .LINK - https://help.alteryx.com/current/product-activation-and-licensing/use-command-line-options + https://help.alteryx.com/current/en/license-and-activate/administer/use-command-line-options.html #> [CmdletBinding ( @@ -55,17 +55,21 @@ function Install-Alteryx { # Get global preference variables Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState # Log function call - Write-Log -Type "DEBUG" -Message $MyInvocation.ScriptName + Write-Log -Type "DEBUG" -Message $MyInvocation.MyCommand.Name + # Process status + $Installprocess = New-ProcessObject -Name $MyInvocation.MyCommand.Name # Variables $ISOTimeStamp = Get-Date -Format "yyyyMMdd_HHmmss" + $MajorVersion = [System.String]::Concat([System.Version]::Parse($Properties.Version).Major, ".", [System.Version]::Parse($Properties.Version).Minor) $Tags = [Ordered]@{"Version" = $Properties.Version} # Filenames - if ($InstallationProperties.Product -eq "Designer") { + if ($Properties.Product -eq "Designer") { $ServerInstaller = "AlteryxInstallx64_.exe" } else { $ServerInstaller = "AlteryxServerInstallx64_.exe" } - # $RFileName AISFileName + # Add-ons paths + $RDirectory = Join-Path -Path $Properties.InstallationPath -ChildPath "RInstaller" $RInstaller = "RInstaller_.exe" $AISInstaller = "AlteryxAISInstall_.exe" # Unattended execution arguments @@ -76,93 +80,112 @@ function Install-Alteryx { } } Process { - Write-Log -Type "INFO" -Message "Installation of Alteryx $($InstallationProperties.Product) $($Properties.Version)" + $Installprocess = Update-ProcessObject -ProcessObject $Installprocess -Status "Running" + Write-Log -Type "INFO" -Message "Installation of Alteryx $($Properties.Product) $($Properties.Version)" # ------------------------------------------------------------------------------ - # Alteryx Server + # * Alteryx Server # ------------------------------------------------------------------------------ - if ($InstallationProperties.Product -eq "Designer" -Or $InstallationProperties.Server -eq $true) { + if ($Properties.Product -eq "Designer" -Or $InstallationProperties.Server -eq $true) { # Update file version number $ServerFileName = Set-Tags -String $ServerInstaller -Tags (Resolve-Tags -Tags $Tags -Prefix "<" -Suffix ">") $ServerPath = Join-Path -Path $Properties.SrcDirectory -ChildPath $ServerFileName if (Test-Object -Path $ServerPath -NotFound) { + $DefaultServerPath = $ServerPath # Workaround for files not following naming convention due to duplicate pipeline runs $Workaround = [Ordered]@{"Version" = [System.String]::Concat($Properties.Version, "_1")} $ServerFileName = Set-Tags -String $ServerInstaller -Tags (Resolve-Tags -Tags $Workaround -Prefix "<" -Suffix ">") $ServerPath = Join-Path -Path $Properties.SrcDirectory -ChildPath $ServerFileName + if (Test-Object -Path $ServerPath -NotFound) { + Write-Log -Type "ERROR" -Message "Path not found $DefaultServerPath" + Write-Log -Type "ERROR" -Message "Alteryx $($Properties.Product) installation file could not be located" + Write-Log -Type "WARN" -Message "Alteryx installation failed" + $Installprocess = Update-ProcessObject -ProcessObject $Installprocess -Status "Failed" -ErrorCount 1 -ExitCode 1 + return $Installprocess + } else { + Write-Log -Type "DEBUG" -Message "Path not found $DefaultServerPath" + } } - Write-Log -Type "INFO" -Message "Installing Alteryx $($InstallationProperties.Product)" + Write-Log -Type "INFO" -Message "Installing Alteryx $($Properties.Product)" if ($PSCmdlet.ShouldProcess($ServerPath, "Install")) { if (Test-Path -Path $ServerPath) { if ($Properties.InstallAwareLog -eq $true) { $InstallAwareLog = Join-Path -Path $Properties.LogDirectory -ChildPath "${ISOTimeStamp}_${ServerFileName}.log" if ($null -eq $Properties.LicenseEmail -Or $Properties.LicenseEmail -eq "") { - $ServerInstall = Install-AlteryxServer -Path $ServerPath -InstallDirectory $Properties.InstallationPath -Log $InstallAwareLog -Language $Properties.Language -AllUsers -Unattended:$Unattended + $ServerInstall = Install-AlteryxServer -Path $ServerPath -InstallDirectory $Properties.InstallationPath -Log $InstallAwareLog -Language $Properties.Language -Version $Properties.Version -AllUsers -Unattended:$Unattended } else { - $ServerInstall = Install-AlteryxServer -Path $ServerPath -InstallDirectory $Properties.InstallationPath -Log $InstallAwareLog -Serial $Properties.LicenseEmail -Language $Properties.Language -AllUsers -Unattended:$Unattended + $ServerInstall = Install-AlteryxServer -Path $ServerPath -InstallDirectory $Properties.InstallationPath -Log $InstallAwareLog -Serial $Properties.LicenseEmail -Language $Properties.Language -Version $Properties.Version -AllUsers -Unattended:$Unattended } } else { if ($null -eq $Properties.LicenseEmail -Or $Properties.LicenseEmail -eq "") { - $ServerInstall = Install-AlteryxServer -Path $ServerPath -InstallDirectory $Properties.InstallationPath -Language $Properties.Language -AllUsers -Unattended:$Unattended + $ServerInstall = Install-AlteryxServer -Path $ServerPath -InstallDirectory $Properties.InstallationPath -Language $Properties.Language -Version $Properties.Version -AllUsers -Unattended:$Unattended } else { - $ServerInstall = Install-AlteryxServer -Path $ServerPath -InstallDirectory $Properties.InstallationPath -Serial $Properties.LicenseEmail -Language $Properties.Language -AllUsers -Unattended:$Unattended + $ServerInstall = Install-AlteryxServer -Path $ServerPath -InstallDirectory $Properties.InstallationPath -Serial $Properties.LicenseEmail -Language $Properties.Language -Version $Properties.Version -AllUsers -Unattended:$Unattended } } Write-Log -Type "DEBUG" -Message $ServerInstall if ($ServerInstall.ExitCode -eq 0) { - Write-Log -Type "CHECK" -Message "Alteryx $($InstallationProperties.Product) installed successfully" + Write-Log -Type "CHECK" -Message "Alteryx $($Properties.Product) installed successfully" } else { Write-Log -Type "ERROR" -Message "An error occured during the installation" -ExitCode $ServerInstall.ExitCode } } else { Write-Log -Type "ERROR" -Message "Path not found $ServerPath" - Write-Log -Type "ERROR" -Message "Alteryx $($InstallationProperties.Product) installation file could not be located" - Write-Log -Type "WARN" -Message "Alteryx installation failed" -ExitCode 1 - } - } - # Configuration - if ($InstallationProperties.Product -eq "Server") { - if ($Unattended) { - Write-Log -Type "WARN" -Message "Do not forget to configure system settings" - } else { - # TODO start system settings - Write-Log -Type "WARN" -Message "Do not forget to configure system settings" + Write-Log -Type "ERROR" -Message "Alteryx $($Properties.Product) installation file could not be located" + Write-Log -Type "WARN" -Message "Alteryx installation failed" + $Installprocess = Update-ProcessObject -ProcessObject $Installprocess -Status "Failed" -ErrorCount 1 -ExitCode 1 + return $Installprocess } } } # ------------------------------------------------------------------------------ - # Predictive Tools + # * Predictive Tools # ------------------------------------------------------------------------------ if ($InstallationProperties.PredictiveTools -eq $true) { + $RInstall = $true # Update file version number $RFileName = Set-Tags -String $RInstaller -Tags (Resolve-Tags -Tags $Tags -Prefix "<" -Suffix ">") - if ($InstallationProperties.Product -eq "Server") { + if ($Properties.Product -eq "Server") { # Use embedded R installer - $RPath = Join-Path -Path $Properties.InstallationPath -ChildPath "RInstaller\$RFileName" - } elseif ($InstallationProperties.Product -eq "Designer") { + $RPath = Join-Path -Path $RDirectory -ChildPath $RFileName + } elseif ($Properties.Product -eq "Designer") { $RPath = Join-Path -Path $Properties.SrcDirectory -ChildPath $RFileName } # Check source file Write-Log -Type "INFO" -Message "Installing Predictive Tools" if ($PSCmdlet.ShouldProcess($RPath, "Install")) { - if (Test-Path -Path $RPath) { + if (Test-Object -Path $RPath -NotFound) { + # Look for a file which may not match the patch versioning + $RFile = Get-ChildItem -Path $RDirectory -Filter "RInstaller_*.exe" + if (($Properties.Product -eq "Server") -Or (($RFile | Measure-Object).Count) -eq 1) { + $RPath = $RFile.FullName + } else { + Write-Log -Type "ERROR" -Message "Path not found $RPath" + Write-Log -Type "ERROR" -Message "Predictive Tools installation file could not be located" + Write-Log -Type "WARN" -Message "Predictive Tools installation failed" + $Installprocess = Update-ProcessObject -ProcessObject $Installprocess -ErrorCount 1 + $RInstall = $false + } + } + if (($RInstall = $true) -And (Test-Object -Path $RPath)) { $RCommand = (@("&", $RPath, $Arguments) -join " ").Trim() Write-Log -Type "DEBUG" -Message $RCommand - $RInstall = Start-Process -FilePath $RPath -ArgumentList $Arguments -Verb "RunAs" -PassThru -Wait + if ($Unattended) { + $RInstall = Start-Process -FilePath $RPath -ArgumentList $Arguments -Verb "RunAs" -PassThru -Wait + } else { + $RInstall = Start-Process -FilePath $RPath -Verb "RunAs" -PassThru -Wait + } Write-Log -Type "DEBUG" -Message $RInstall if ($RInstall.ExitCode -eq 0) { Write-Log -Type "CHECK" -Message "Predictive Tools installed successfully" } else { - Write-Log -Type "ERROR" -Message "An error occured during the installation" -ExitCode $RInstall.ExitCode + Write-Log -Type "ERROR" -Message "An error occured during the installation" + $Installprocess = Update-ProcessObject -ProcessObject $Installprocess -ErrorCount 1 } - } else { - Write-Log -Type "ERROR" -Message "Path not found $RPath" - Write-Log -Type "ERROR" -Message "Predictive Tools installation file could not be located" - Write-Log -Type "WARN" -Message "Predictive Tools installation failed" -ExitCode 1 } } } # ------------------------------------------------------------------------------ - # Intelligence Suite + # * Intelligence Suite # ------------------------------------------------------------------------------ if ($InstallationProperties.IntelligenceSuite -eq $true) { # Update file version number @@ -170,16 +193,33 @@ function Install-Alteryx { $AISPath = Join-Path -Path $Properties.SrcDirectory -ChildPath $AISFileName if (Test-Object -Path $AISPath -NotFound) { # Workaround for files not following naming convention due to duplicate pipeline runs - $Workaround = [Ordered]@{"Version" = [System.String]::Concat($Properties.Version, "_1")} - $AISFileName = Set-Tags -String $AISInstaller -Tags (Resolve-Tags -Tags $Workaround -Prefix "<" -Suffix ">") - $AISPath = Join-Path -Path $Properties.SrcDirectory -ChildPath $AISFileName + $Workaround = [Ordered]@{"Version" = [System.String]::Concat($Properties.Version, "_1")} + $WorkaroundPath = Set-Tags -String $AISInstaller -Tags (Resolve-Tags -Tags $Workaround -Prefix "<" -Suffix ">") + if (Test-Path -Path $WorkaroundPath) { + $AISFileName = $WorkaroundPath + $AISPath = Join-Path -Path $Properties.SrcDirectory -ChildPath $AISFileName + } else { + # Check latest file that matches the major version + $Check = [Ordered]@{"Version" = "$MajorVersion.*"} + $CheckPattern = Set-Tags -String $AISInstaller -Tags (Resolve-Tags -Tags $Check -Prefix "<" -Suffix ">") + $CheckPath = Join-Path -Path $Properties.SrcDirectory -ChildPath $CheckPattern + $CheckFile = Get-ChildItem -Path $CheckPath | Sort-Object -Property "LastWriteTime" -Descending | Select-Object -First 1 + if ($null -ne $CheckFile) { + $AISFileName = $CheckFile.Name + $AISPath = $CheckFile.FullName + } + } } Write-Log -Type "INFO" -Message "Installing Intelligence Suite" if ($PSCmdlet.ShouldProcess($AISPath, "Install")) { if (Test-Path -Path $AISPath) { $AISCommand = (@("&", $AISPath, $Arguments) -join " ").Trim() Write-Log -Type "DEBUG" -Message $AISCommand - $AISInstall = Start-Process -FilePath $AISPath -ArgumentList $Arguments -Verb "RunAs" -PassThru -Wait + if ($Unattended) { + $AISInstall = Start-Process -FilePath $AISPath -ArgumentList $Arguments -Verb "RunAs" -PassThru -Wait + } else { + $AISInstall = Start-Process -FilePath $AISPath -Verb "RunAs" -PassThru -Wait + } Write-Log -Type "DEBUG" -Message $AISInstall if ($AISInstall.ExitCode -eq 0) { Write-Log -Type "CHECK" -Message "Intelligence Suite installed successfully" @@ -189,70 +229,111 @@ function Install-Alteryx { } else { Write-Log -Type "ERROR" -Message "Path not found $AISPath" Write-Log -Type "ERROR" -Message "Intelligence Suite installation file could not be located" - Write-Log -Type "WARN" -Message "Intelligence Suite installation failed" -ExitCode 1 + Write-Log -Type "WARN" -Message "Intelligence Suite installation failed" + $Installprocess = Update-ProcessObject -ProcessObject $Installprocess -ErrorCount 1 } } } # ------------------------------------------------------------------------------ - # Data packages + # * Data packages # ------------------------------------------------------------------------------ + # TODO if ($InstallationProperties.DataPackages -eq $true) { - # TODO - $DataPackage = $null - if ($null -ne $DataPackage) { - $DataPackagePath = Join-Path -Path $Properties.SrcDirectory -ChildPath "$DataPackage.7z" - $DataPackageLog = Join-Path -Path $Properties.LogDirectory -ChildPath "${ISOTimeStamp}_${DataPackage}.log" - if (-Not (Test-Path -Path $DataPackagePath)) { - Write-Log -Type "ERROR" -Message "Path not found $DataPackagePath" - Write-Log -Type "ERROR" -Message "Data package could not be located" - Write-Log -Type "WARN" -Message "Data package installation failed" -ExitCode 1 - } - # Unzip data package - $Destination = $DataPackagePath.Replace('.7z', '') - if (Test-Path -Path $Destination) { - Write-Log -Type "WARN" -Message "Path already exists $Destination" - Write-Log -Type "INFO" -Message "Skipping data package unzip" - } else { - $7zip = "& ""$($Properties.'7zipPath')"" x ""$DataPackagePath"" -o$Destination -y" - Write-Log -Type "DEBUG" -Message $7zip - if (Test-Path -Path $Properties.'7zipPath') { - if ($PSCmdlet.ShouldProcess($DataPackagePath, "Unzip")) { - $Unzip = Invoke-Expression -Command $7zip | Out-String - } - } else { - Write-Log -Type "ERROR" -Message "Path not found $($Properties.'7zipPath')" - Write-Log -Type "ERROR" -Message "7zip could not be located" - Write-Log -Type "WARN" -Message "Data package installation failed" -ExitCode 1 + if ($PSCmdlet.ShouldProcess("Alteryx Data Packages", "Install")) { + $InstallDataPackage = $true + $DataPackage = $null + if ($null -ne $DataPackage) { + $DataPackagePath = Join-Path -Path $Properties.SrcDirectory -ChildPath "$DataPackage.7z" + $DataPackageLog = Join-Path -Path $Properties.LogDirectory -ChildPath "${ISOTimeStamp}_${DataPackage}.log" + if (-Not (Test-Path -Path $DataPackagePath)) { + Write-Log -Type "ERROR" -Message "Path not found $DataPackagePath" + Write-Log -Type "ERROR" -Message "Data package could not be located" + Write-Log -Type "WARN" -Message "Data package installation failed" + $Installprocess = Update-ProcessObject -ProcessObject $Installprocess -ErrorCount 1 + $InstallDataPackage = false } - # ! TODO check unzip - Write-Log -Type "DEBUG" -Message $Unzip - } - # Check unzip outcome - if (Test-Path -Path $Destination) { - # Run installer - $DataPackageInstaller = Join-Path -Path $Destination -ChildPath "DataInstallCmd.exe" - if (Test-Path -Path $DataPackageInstaller) { - Install-AlteryxDataPackage -Path $DataPackageInstaller -InstallDirectory $Properties.DataPackagesPath -Log $DataPackageLog -Action "Install" -Unattended:$Unattended + if ($InstallDataPackage -eq $true) { + # Unzip data package + $Destination = $DataPackagePath.Replace('.7z', '') + if (Test-Path -Path $Destination) { + Write-Log -Type "WARN" -Message "Path already exists $Destination" + Write-Log -Type "INFO" -Message "Skipping data package unzip" + } else { + $7zip = "& ""$($Properties.'7zipPath')"" x ""$DataPackagePath"" -o$Destination -y" + Write-Log -Type "DEBUG" -Message $7zip + if (Test-Path -Path $Properties.'7zipPath') { + if ($PSCmdlet.ShouldProcess($DataPackagePath, "Unzip")) { + $Unzip = Invoke-Expression -Command $7zip | Out-String + } + } else { + Write-Log -Type "ERROR" -Message "Path not found $($Properties.'7zipPath')" + Write-Log -Type "ERROR" -Message "7zip could not be located" + Write-Log -Type "WARN" -Message "Data package installation failed" + $Installprocess = Update-ProcessObject -ProcessObject $Installprocess -ErrorCount 1 + $InstallDataPackage = false + } + # ! TODO check unzip + Write-Log -Type "DEBUG" -Message $Unzip + } + # Check unzip outcome + if (Test-Path -Path $Destination) { + # Run installer + $DataPackageInstaller = Join-Path -Path $Destination -ChildPath "DataInstallCmd.exe" + if (Test-Path -Path $DataPackageInstaller) { + Install-AlteryxDataPackage -Path $DataPackageInstaller -InstallDirectory $Properties.DataPackagesPath -Log $DataPackageLog -Action "Install" -Unattended:$Unattended + } + } else { + Write-Log -Type "ERROR" -Message "Path not found $Destination" + Write-Log -Type "ERROR" -Message "Data package unzipping failed" + Write-Log -Type "WARN" -Message "Data package installation failed" + $Installprocess = Update-ProcessObject -ProcessObject $Installprocess -ErrorCount 1 + } } } else { - Write-Log -Type "ERROR" -Message "Path not found $Destination" - Write-Log -Type "ERROR" -Message "Data package unzipping failed" - Write-Log -Type "WARN" -Message "Data package installation failed" -ExitCode 1 + Write-Log -Type "DEBUG" -Message "No data package specified" + Write-Log -Type "WARN" -Message "Skipping data package installation" } - } else { - Write-Log -Type "DEBUG" -Message "No data package specified" } } # ------------------------------------------------------------------------------ - # Licensing + # * Licensing # ------------------------------------------------------------------------------ if ($Properties.ActivateOnInstall -eq $true) { - Invoke-ActivateAlteryx -Properties $Properties -Unattended:$Unattended + if ($PSCmdlet.ShouldProcess("Alteryx license", "Activate")) { + $ActivateProcess = Invoke-ActivateAlteryx -Properties $Properties -Unattended:$Unattended + if ($ActivateProcess.Success -eq $false) { + $Installprocess = Update-ProcessObject -ProcessObject $Installprocess -ErrorCount $ActivateProcess.ErrorCount + } + } } else { Write-Log -Type "WARN" -Message "Skipping license activation" } + # ------------------------------------------------------------------------------ + # * Configuration + # ------------------------------------------------------------------------------ + if ($Properties.Product -eq "Server") { + $ConfigureProcess = Set-AlteryxConfiguration -Properties $Properties + if ($ConfigureProcess.Success -eq $false) { + $Installprocess = Update-ProcessObject -ProcessObject $Installprocess -ErrorCount $ConfigureProcess.ErrorCount + } + } + # ------------------------------------------------------------------------------ + # * Check + # ------------------------------------------------------------------------------ + if ($Installprocess.ErrorCount -eq 0) { + Write-Log -Type "CHECK" -Message "Alteryx $($Properties.Product) $($Properties.Version) installed successfully" + $Installprocess = Update-ProcessObject -ProcessObject $Installprocess -Status "Completed" -Success $true + } else { + if ($Installprocess.ErrorCount -eq 1) { + $ErrorCount = "one error" + } else { + $ErrorCount = "$($Installprocess.ErrorCount) errors" + } + Write-Log -Type "WARN" -Message "Alteryx $($Properties.Product) $($Properties.Version) was installed with $ErrorCount" + $Installprocess = Update-ProcessObject -ProcessObject $Installprocess -Status "Completed" + } } End { - Write-Log -Type "CHECK" -Message "Alteryx $($InstallationProperties.Product) $($Properties.Version) installed successfully" + return $Installprocess } } \ No newline at end of file diff --git a/powershell/Invoke-ActivateAlteryx.ps1 b/powershell/Invoke-ActivateAlteryx.ps1 index aca5aea..66bf3fa 100644 --- a/powershell/Invoke-ActivateAlteryx.ps1 +++ b/powershell/Invoke-ActivateAlteryx.ps1 @@ -16,13 +16,13 @@ function Invoke-ActivateAlteryx { File name: Invoke-ActivateAlteryx.ps1 Author: Florian Carrier Creation date: 2021-07-05 - Last modified: 2022-04-19 + Last modified: 2024-10-08 .LINK https://www.powershellgallery.com/packages/PSAYX .LINK - https://help.alteryx.com/current/product-activation-and-licensing/use-command-line-options + https://help.alteryx.com/current/en/license-and-activate/administer/use-command-line-options.html #> [CmdletBinding ( SupportsShouldProcess = $true @@ -46,54 +46,96 @@ function Invoke-ActivateAlteryx { # Get global preference variables Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState # Log function call - Write-Log -Type "DEBUG" -Message $MyInvocation.ScriptName + Write-Log -Type "DEBUG" -Message $MyInvocation.MyCommand.Name + # Process status + $ActivateProcess = New-ProcessObject -Name $MyInvocation.MyCommand.Name # License utility $LicenseUtility = Get-AlteryxUtility -Utility "License" -Path $Properties.InstallationPath + # Expected license format + $LicensePattern = "\w{4}-\w{4}-\w{4}-\w{4}-\w{4}-\w{4}-\w{4}-\w{4}" } Process { - Write-Log -Type "INFO" -Message "Activating Alteryx" + $ActivateProcess = Update-ProcessObject -ProcessObject $ActivateProcess -Status "Running" + Write-Log -Type "NOTICE" -Message "Activating Alteryx" if ($PSCmdlet.ShouldProcess("Alteryx", "Activate")) { # Check licensing system connectivity Write-Log -Type "INFO" -Message "Checking licensing system connectivity" if ((Test-HTTPStatus -URI $Properties.LicensingURL) -eq $true) { - # Check license key(s) + # Check license keys + Write-Log -Type "INFO" -Message "Checking license keys" if (-Not (Find-Key -Hashtable $Properties -Key "LicenseKey")) { # Check license file if ($null -eq $Properties.LicenseFile -Or $Properties.LicenseFile -eq "") { Write-Log -Type "ERROR" -Message "No license key or file have been specified" - Write-Log -Type "WARN" -Message "Alteryx product activation failed" -ExitCode 1 + Write-Log -Type "WARN" -Message "Alteryx product activation failed" + $ActivateProcess = Update-ProcessObject -ProcessObject $ActivateProcess -Status "Failed" -ErrorCount 1 -ExitCode 1 + return $ActivateProcess } # Read keys from license file - if (Test-Object -Path $Properties.LicenseFile -NotFound) { - Write-Log -Type "ERROR" -Message "License file path not found $($Properties.LicenseFile)" - Write-Log -Type "WARN" -Message "Alteryx product activation failed" -ExitCode 1 + $LicenseFilePath = Join-Path -Path $Properties.ResDirectory -ChildPath $Properties.LicenseFile + if (Test-Object -Path $LicenseFilePath -NotFound) { + Write-Log -Type "ERROR" -Message "License file does not exist $($Properties.LicenseFile)" + Write-Log -Type "WARN" -Message "Alteryx product activation failed" + $ActivateProcess = Update-ProcessObject -ProcessObject $ActivateProcess -Status "Failed" -ErrorCount 1 -ExitCode 1 + return $ActivateProcess } - $Properties.LicenseKey = @(Get-Content -Path $Properties.LicenseFile) + $Properties.LicenseKey = (ConvertFrom-SecureString -SecureString (ConvertTo-SecureString -String (Get-Content -Path $LicenseFilePath)) -AsPlainText) -split '[ ,]+' } + Write-Log -Type "DEBUG" -Message $Properties.LicenseKey # Count keys - if ($Properties.LicenseKey.Count -gt 1) { - $Success = "$($Properties.LicenseKey.Count) licenses were successfully activated" - $Failure = "Licenses could not be activated" - $Grammar = "licenses" - $Properties.LicenseKey = $Properties.LicenseKey -join " " - } elseif ($Properties.LicenseKey.Count -eq 1) { - $Success = "$($Properties.LicenseKey.Count) license was successfully activated" - $Failure = "License could not be activated" - $Grammar = "license" - } else { + if ($Properties.LicenseKey.Count -eq 0) { Write-Log -Type "ERROR" -Message "No license key was provided" - Write-Log -Type "WARN" -Message "Alteryx product activation failed" -ExitCode 1 + Write-Log -Type "WARN" -Message "Alteryx product activation failed" + $ActivateProcess = Update-ProcessObject -ProcessObject $ActivateProcess -Status "Failed" -ErrorCount 1 -ExitCode 1 + return $ActivateProcess + } else { + # Check existing licenses keys + $CurrentLicenses = Get-AlteryxLicense -Path $LicenseUtility + $NewLicenses = New-Object -TypeName "System.Collections.ArrayList" + if ($CurrentLicenses | Select-String -Pattern "\b$LicensePattern\b" -Quiet) { + $CurrentKeys = ($CurrentLicenses | Select-String -Pattern "\b$LicensePattern\b" -AllMatches).Matches.Value + foreach ($Key in $Properties.LicenseKey) { + if ($CurrentKeys.Contains($Key)) { + Write-Log -Type "WARN" -Message "$Key is already activated" + } elseif ($Key -notmatch "^$LicensePattern$") { + Write-Log -Type "ERROR" -Message "$Key key does not match the expected format" + $ActivateProcess = Update-ProcessObject -ProcessObject $ActivateProcess -ErrorCount 1 + Write-Log -Type "WARN" -Message "Skipping license $Key" + } else { + $NewLicenses.Add($Key) + } + } + } else { + $NewLicenses.AddRange($Properties.LicenseKey) + } + } + # Check remaining list of keys + if ($NewLicenses.Count -eq 0) { + if ($ActivateProcess.ErrorCount -ge 1) { + Write-Log -Type "ERROR" -Message "No valid license key was provided" + Write-Log -Type "WARN" -Message "Alteryx product activation failed" + $ActivateProcess = Update-ProcessObject -ProcessObject $ActivateProcess -Status "Failed" -ExitCode 1 + } else { + Write-Log -Type "WARN" -Message "No new license key was provided" + Write-Log -Type "WARN" -Message "Skipping license activation process" + $ActivateProcess = Update-ProcessObject -ProcessObject $ActivateProcess -Status "Completed" -Success 1 + } + return $ActivateProcess } # Check email address - if ($null -eq $Properties.LicenseEmail -Or $Properties.LicenseEmail -eq "") { + $Email = $Properties.LicenseEmail + if ($null -eq $Email -Or $Email -eq "") { if ($Unattended) { Write-Log -Type "ERROR" -Message "No email address provided for license activation" + $ActivateProcess = Update-ProcessObject -ProcessObject $ActivateProcess -ErrorCount 1 Write-Log -Type "WARN" -Message "Retrieving email address associated with current session through Windows Active Directory" try { $Email = Get-ADUser -Identity $env:UserName -Properties "mail" | Select-Object -ExpandProperty "mail" Write-Log -Type "DEBUG" -Message $Email } catch { Write-Log -Type "ERROR" -Message $Error[0].Exception + $ActivateProcess = Update-ProcessObject -ProcessObject $ActivateProcess -ErrorCount 1 + Write-Log -Type "INFO" -Message "Failed to retrieve email from active session" $Email = $null } } else { @@ -103,29 +145,63 @@ function Invoke-ActivateAlteryx { } until ($Email -as [System.Net.Mail.MailAddress]) } } else { - $Email = $Properties.LicenseEmail + if ($Email -as [System.Net.Mail.MailAddress]) { + $Email = $Properties.LicenseEmail + } else { + if ($null -eq $Email) { + Write-Log -Type "ERROR" -Message "Email address is missing" + } else { + Write-Log -Type "ERROR" -Message "Email address is invalid" + } + Write-Log -Type "WARN" -Message "Skipping product activation" + $ActivateProcess = Update-ProcessObject -ProcessObject $ActivateProcess -Status "Failed" -ErrorCount 1 -ExitCode 1 + return $ActivateProcess + } + } + # Call license utility + $Count = 0 + foreach ($Key in $NewLicenses) { + Write-Log -Type "INFO" -Message "Activating license $Key" + if ($Key -match "^$LicensePattern$") { + $Activation = Add-AlteryxLicense -Path $LicenseUtility -Key $Key -Email $Email + # Check activation status + if (Select-String -InputObject $Activation -Pattern "License(s) successfully activated." -SimpleMatch -Quiet) { + Write-Log -Type "CHECK" -Message "License key $Key successfully activated" + $Count += 1 + } else { + Write-Log -Type "ERROR" -Message $Activation + $ActivateProcess = Update-ProcessObject -ProcessObject $ActivateProcess -ErrorCount 1 + } + } else { + Write-Log -Type "ERROR" -Message "$Key does not match the expected Alteryx license key format" + $ActivateProcess = Update-ProcessObject -ProcessObject $ActivateProcess -ErrorCount 1 + } + } - # TODO check email format - if ($Email -as [System.Net.Mail.MailAddress]) { - # Call license utility - Write-Log -Type "INFO" -Message "Activating $Grammar" - $Activation = Add-AlteryxLicense -Path $LicenseUtility -Key $Properties.LicenseKey -Email $Email - # Check activation status - if (Select-String -InputObject $Activation -Pattern "License(s) successfully activated." -SimpleMatch -Quiet) { - Write-Log -Type "CHECK" -Message $Success + # Check activation results + if ($Count -eq $NewLicenses.Count) { + if ($NewLicenses.Count -eq 1) { + $Success = "$($NewLicenses.Count) license was successfully activated" } else { - # Output error and stop script - Write-Log -Type "ERROR" -Message $Activation - Write-Log -Type "ERROR" -Message $Failure -ExitCode 1 + $Success = "$($NewLicenses.Count) licenses were successfully activated" } + Write-Log -Type "CHECK" -Message $Success + $ActivateProcess = Update-ProcessObject -ProcessObject $ActivateProcess -Status "Completed" -Success $true + } elseif ($Count -gt 0) { + Write-Log -Type "WARN" -Message "$Count out of $($NewLicenses.Count) could not be activated" + $ActivateProcess = Update-ProcessObject -ProcessObject $ActivateProcess -Status "Completed" } else { - Write-Log -Type "ERROR" -Message "Email address is missing or invalid" - Write-Log -Type "WARN" -Message "Skipping product activation" + Write-Log -Type "WARN" -Message "No license could not be activated" + $ActivateProcess = Update-ProcessObject -ProcessObject $ActivateProcess -Status "Failed" -ExitCode 1 } } else { Write-Log -Type "ERROR" -Message "Unable to reach licensing system" Write-Log -Type "WARN" -Message "Skipping product activation" + $ActivateProcess = Update-ProcessObject -ProcessObject $ActivateProcess -Status "Failed" -ErrorCount 1 -ExitCode 1 } } } + End { + return $ActivateProcess + } } \ No newline at end of file diff --git a/powershell/Invoke-BackupAlteryx.ps1 b/powershell/Invoke-BackupAlteryx.ps1 index 6b72518..8adc0c5 100644 --- a/powershell/Invoke-BackupAlteryx.ps1 +++ b/powershell/Invoke-BackupAlteryx.ps1 @@ -10,7 +10,7 @@ function Invoke-BackupAlteryx { File name: Invoke-BackupAlteryx.ps1 Author: Florian Carrier Creation date: 2021-08-26 - Last modified: 2022-04-19 + Last modified: 2024-09-16 #> [CmdletBinding ( SupportsShouldProcess = $true @@ -34,11 +34,14 @@ function Invoke-BackupAlteryx { # Get global preference vrariables Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState # Log function call - Write-Log -Type "DEBUG" -Message $MyInvocation.ScriptName + Write-Log -Type "DEBUG" -Message $MyInvocation.MyCommand.Name + # Process status + $BackupProcess = New-ProcessObject -Name $MyInvocation.MyCommand.Name # Variables + $Version = Get-AlteryxVersion $ISOTimeStamp = Get-Date -Format "yyyyMMdd_HHmmss" - $BackupPath = Join-Path -Path $Properties.BackupDirectory -ChildPath "${ISOTimeStamp}_Alteryx_Server_$($Properties.Version).zip" - $TempBackupPath = Join-Path -Path $Properties.TempDirectory -ChildPath "${ISOTimeStamp}_Alteryx_Server_$($Properties.Version)" + $BackupPath = Join-Path -Path $Properties.BackupDirectory -ChildPath "${ISOTimeStamp}_Alteryx_Server_$($Version).zip" + $TempBackupPath = Join-Path -Path $Properties.TempDirectory -ChildPath "${ISOTimeStamp}_Alteryx_Server_$($Version)" $MongoDBPath = Join-Path -Path $TempBackupPath -ChildPath "MongoDB" $ServicePath = Join-Path -Path $Properties.InstallationPath -ChildPath "bin\AlteryxService.exe" # Backup options @@ -62,9 +65,10 @@ function Invoke-BackupAlteryx { } } Process { - Write-Log -Type "CHECK" -Message "Start $BackupType backup of Alteryx Server" + $BackupProcess = Update-ProcessObject -ProcessObject $BackupProcess -Status "Running" + Write-Log -Type "NOTICE" -Message "Start $BackupType backup of Alteryx Server" # ---------------------------------------------------------------------------- - # Check Alteryx service status + # * Check Alteryx service status Write-Log -Type "INFO" -Message "Check Alteryx Service status" if ($PSCmdlet.ShouldProcess("Alteryx Service", "Stop")) { $Service = "AlteryxService" @@ -73,14 +77,20 @@ function Invoke-BackupAlteryx { Write-Log -Type "DEBUG" -Message $Service $ServiceStatus = $WindowsService.Status if ($ServiceStatus -eq "Running") { - Invoke-StopAlteryx -Properties $Properties -Unattended:$Unattended + $StopProcess = Invoke-StopAlteryx -Properties $Properties -Unattended:$Unattended + if ($StopProcess.Success -eq $false) { + $BackupProcess = Update-ProcessObject -ProcessObject $BackupProcess -Status $StopProcess.Status -ErrorCount $StopProcess.ErrorCount -ExitCode $StopProcess.ExitCode + return $BackupProcess + } } } else { - Write-Log -Type "ERROR" -Message "Alteryx Service ($Service) could not be found" -ExitCode 1 + Write-Log -Type "ERROR" -Message "Alteryx Service ($Service) could not be found" + $BackupProcess = Update-ProcessObject -ProcessObject $BackupProcess -Status "Failed" -ErrorCount 1 -ExitCode 1 + return $BackupProcess } } # ---------------------------------------------------------------------------- - # Create database dump + # * Create database dump if ($Backup.Database -eq $true) { Write-Log -Type "INFO" -Message "Create MongoDB database backup" if ($PSCmdlet.ShouldProcess("MongoDB", "Back-up")) { @@ -91,7 +101,9 @@ function Invoke-BackupAlteryx { $DatabaseBackup = Backup-AlteryxDatabase -Path $MongoDBPath -ServicePath $ServicePath if ($DatabaseBackup -match "EMongoDump failed") { Write-Log -Type "ERROR" -Message "$DatabaseBackup" - Write-Log -Type "ERROR" -Message "Database backup failed" -ExitCode 1 + Write-Log -Type "ERROR" -Message "Database backup failed" + $BackupProcess = Update-ProcessObject -ProcessObject $BackupProcess -Status "Failed" -ErrorCount 1 -ExitCode 1 + return $BackupProcess } else { Write-Log -Type "DEBUG" -Message "$DatabaseBackup" } @@ -101,7 +113,7 @@ function Invoke-BackupAlteryx { } } # ---------------------------------------------------------------------------- - # Backup configuration files + # * Backup configuration files if ($Backup.Configuration -eq $true) { Write-Log -Type "INFO" -Message "Backup configuration files" if ($PSCmdlet.ShouldProcess("Configuration files", "Back-up")) { @@ -129,7 +141,7 @@ function Invoke-BackupAlteryx { } } # ---------------------------------------------------------------------------- - # Backup tokens + # * Backup tokens if ($Backup.Token -eq $true) { Write-Log -Type "INFO" -Message "Backup controller token" if ($PSCmdlet.ShouldProcess("Controller token", "Back-up")) { @@ -141,7 +153,7 @@ function Invoke-BackupAlteryx { } } # ---------------------------------------------------------------------------- - # Compress backup + # * Compress backup Write-Log -Type "INFO" -Message "Compress backup files" if ($PSCmdlet.ShouldProcess("Compress", "Back-up")) { if (Test-Object -Path $Properties.BackupDirectory -NotFound) { @@ -150,7 +162,8 @@ function Invoke-BackupAlteryx { Compress-Archive -Path "$TempBackupPath\*" -DestinationPath $BackupPath -CompressionLevel "Optimal" -WhatIf:$WhatIfPreference Write-Log -Type "DEBUG" -Message $BackupPath if (Test-Object -Path $BackupPath -NotFound) { - Write-Log -Type "ERROR" -Message "Backup files compression failed" -ErrorCode 1 + Write-Log -Type "ERROR" -Message "Backup files compression failed" + $BackupProcess = Update-ProcessObject -ProcessObject $BackupProcess -ErrorCount 1 } else { # Remove staging folder Write-Log -Type "INFO" -Message "Remove staging backup folder" @@ -160,13 +173,17 @@ function Invoke-BackupAlteryx { } } # ---------------------------------------------------------------------------- - # Restart service if it was running before + # * Restart service if it was running before if ($ServiceStatus -eq "Running") { - Invoke-StartAlteryx -Properties $Properties -Unattended:$Unattended + $StartProcess = Invoke-StartAlteryx -Properties $Properties -Unattended:$Unattended + if ($StartProcess.Success -eq $false) { + $BackupProcess = Update-ProcessObject -ProcessObject $BackupProcess -ErrorCount 1 + } } + Write-Log -Type "CHECK" -Message "Alteryx Server $BackupType backup complete" + $BackupProcess = Update-ProcessObject -ProcessObject $BackupProcess -Status "Completed" -Success $true } End { - # If no error occured - Write-Log -Type "CHECK" -Message "Alteryx Server $BackupType backup complete" + return $BackupProcess } } \ No newline at end of file diff --git a/powershell/Invoke-DeactivateAlteryx.ps1 b/powershell/Invoke-DeactivateAlteryx.ps1 index 3216c89..79fc2ea 100644 --- a/powershell/Invoke-DeactivateAlteryx.ps1 +++ b/powershell/Invoke-DeactivateAlteryx.ps1 @@ -16,13 +16,13 @@ function Invoke-DeactivateAlteryx { File name: Invoke-DeactivateAlteryx.ps1 Author: Florian Carrier Creation date: 2021-11-20 - Last modified: 2023-01-17 + Last modified: 2024-10-08 .LINK https://www.powershellgallery.com/packages/PSAYX .LINK - https://help.alteryx.com/current/product-activation-and-licensing/use-command-line-options + https://help.alteryx.com/current/en/license-and-activate/administer/use-command-line-options.html #> [CmdletBinding ( SupportsShouldProcess = $true @@ -51,84 +51,124 @@ function Invoke-DeactivateAlteryx { # Get global preference variables Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState # Log function call - Write-Log -Type "DEBUG" -Message $MyInvocation.ScriptName + Write-Log -Type "DEBUG" -Message $MyInvocation.MyCommand.Name + # Process status + $DeactivateProcess = New-ProcessObject -Name $MyInvocation.MyCommand.Name # License utility $LicenseUtility = Get-AlteryxUtility -Utility "License" -Path $Properties.InstallationPath + # Expected license format + $LicensePattern = "\w{4}-\w{4}-\w{4}-\w{4}-\w{4}-\w{4}-\w{4}-\w{4}" } Process { - Write-Log -Type "INFO" -Message "Deactivating Alteryx" + $DeactivateProcess = Update-ProcessObject -ProcessObject $DeactivateProcess -Status "Running" + Write-Log -Type "NOTICE" -Message "Deactivating Alteryx" if ($PSCmdlet.ShouldProcess("Alteryx", "Deactivate")) { - # Check licensing system connectivity - Write-Log -Type "INFO" -Message "Checking licensing system connectivity" - if ((Test-HTTPStatus -URI $Properties.LicensingURL) -eq $true) { - # Deactivate license - if ($All) { - # Remove all license keys - $Deactivation = Remove-AlteryxLicense -Path $LicenseUtility - # Check deactivation status - if (Select-String -InputObject $Deactivation -Pattern "License(s) successfully removed." -SimpleMatch -Quiet) { - Write-Log -Type "CHECK" -Message "All licenses were successfully deactivated" - } else { - # Output error - Write-Log -Type "DEBUG" -Message $Deactivation - } + # Check licensing system connectivity + Write-Log -Type "INFO" -Message "Checking licensing system connectivity" + if ((Test-HTTPStatus -URI $Properties.LicensingURL) -eq $true) { + # Check existing license keys + $CurrentLicenses = Get-AlteryxLicense -Path $LicenseUtility + if ($CurrentLicenses -match "No license keys found.") { + Write-Log -Type "WARN" -Message "No license key is currently activated" + Write-Log -Type "WARN" -Message "Skipping Alteryx product deactivation" + $DeactivateProcess = Update-ProcessObject -ProcessObject $DeactivateProcess -Status "Completed" -Success $true } else { - # Check license key(s) - if (-Not (Find-Key -Hashtable $Properties -Key "LicenseKey")) { - # Check license file - if ($null -eq $Properties.LicenseFile -Or $Properties.LicenseFile -eq "") { - Write-Log -Type "ERROR" -Message "No license key or file have been specified" - Write-Log -Type "WARN" -Message "Alteryx product deactivation failed" -ExitCode 1 - } - # Read keys from license file - if (Test-Object -Path $Properties.LicenseFile -NotFound) { - Write-Log -Type "ERROR" -Message "License file path not found $($Properties.LicenseFile)" - Write-Log -Type "WARN" -Message "Alteryx product deactivation failed" -ExitCode 1 - } - $Properties.LicenseKey = @(Get-Content -Path $Properties.LicenseFile) - } - if ($Properties.LicenseKey.Count -eq 0) { - Write-Log -Type "ERROR" -Message "No license key was provided" - Write-Log -Type "WARN" -Message "Alteryx product deactivation failed" -ExitCode 1 - } - # Deactivate each key - $Count = 0 - foreach ($Key in $Properties.LicenseKey) { - Write-Log -Type "INFO" -Message "Deactivating license key $Key" - $Dectivation = Remove-AlteryxLicense -Path $LicenseUtility -Key $Key + # Deactivate licenses + if ($All) { + # Remove all license keys + $Deactivation = Remove-AlteryxLicense -Path $LicenseUtility # Check deactivation status - if (Select-String -InputObject $Dectivation -Pattern "License(s) successfully removed." -SimpleMatch -Quiet) { - $Count += 1 + if (Select-String -InputObject $Deactivation -Pattern "License(s) successfully removed." -SimpleMatch -Quiet) { + Write-Log -Type "CHECK" -Message "All licenses were successfully deactivated" + $DeactivateProcess = Update-ProcessObject -ProcessObject $DeactivateProcess -Status "Completed" -Success $true } else { # Output error - Write-Log -Type "DEBUG" -Message $Deactivation + Write-Log -Type "ERROR" -Message $Deactivation + $DeactivateProcess = Update-ProcessObject -ProcessObject $DeactivateProcess -Status "Failed" -ErrorCount 1 -ExitCode 1 + return $DeactivateProcess } - } - # Check outcome - if ($Properties.LicenseKey.Count -gt 1) { - $Success = "$($Properties.LicenseKey.Count) licenses were successfully deactivated" - $ErrorCount = $Properties.LicenseKey.Count - $Count - if ($ErrorCount -eq $Properties.LicenseKey.Count) { - $Failure = "None of the licenses could not be deactivated" + } else { + # Check license keys + Write-Log -Type "INFO" -Message "Checking license keys" + if (-Not (Find-Key -Hashtable $Properties -Key "LicenseKey")) { + # Check license file + if ($null -eq $Properties.LicenseFile -Or $Properties.LicenseFile -eq "") { + Write-Log -Type "ERROR" -Message "No license key or file have been specified" + Write-Log -Type "WARN" -Message "Alteryx product deactivation failed" + $DeactivateProcess = Update-ProcessObject -ProcessObject $DeactivateProcess -Status "Failed" -ErrorCount 1 -ExitCode 1 + return $DeactivateProcess + } + # Read keys from license file + $LicenseFilePath = Join-Path -Path $Properties.ResDirectory -ChildPath $Properties.LicenseFile + if (Test-Object -Path $LicenseFilePath -NotFound) { + Write-Log -Type "ERROR" -Message "License file path not found $($Properties.LicenseFile)" + Write-Log -Type "WARN" -Message "Alteryx product deactivation failed" + $DeactivateProcess = Update-ProcessObject -ProcessObject $DeactivateProcess -Status "Failed" -ErrorCount 1 -ExitCode 1 + return $DeactivateProcess + } else { + $Properties.LicenseKey = (ConvertFrom-SecureString -SecureString (ConvertTo-SecureString -String (Get-Content -Path $LicenseFilePath)) -AsPlainText) -split '[ ,]+' + } + } + Write-Log -Type "DEBUG" -Message $Properties.LicenseKey + if ($Properties.LicenseKey.Count -eq 0) { + Write-Log -Type "ERROR" -Message "No license key was provided" + Write-Log -Type "WARN" -Message "Alteryx product deactivation failed" + $DeactivateProcess = Update-ProcessObject -ProcessObject $DeactivateProcess -Status "Failed" -ErrorCount 1 -ExitCode 1 + return $DeactivateProcess + } + # Deactivate each key + $CurrentKeys = ($CurrentLicenses | Select-String -Pattern "\b$LicensePattern\b" -AllMatches).Matches.Value + $Count = 0 + foreach ($Key in $Properties.LicenseKey) { + if ($CurrentKeys.Contains($Key)) { + Write-Log -Type "INFO" -Message "Deactivating license key $Key" + $Dectivation = Remove-AlteryxLicense -Path $LicenseUtility -Key $Key + # Check deactivation status + if (Select-String -InputObject $Dectivation -Pattern "License(s) successfully removed." -SimpleMatch -Quiet) { + Write-Log -Type "CHECK" -Message "License key $Key successfully deactivated" + $Count += 1 + } else { + Write-Log -Type "ERROR" -Message $Deactivation + $DeactivateProcess = Update-ProcessObject -ProcessObject $DeactivateProcess -ErrorCount 1 + } + } else { + Write-Log -Type "ERROR" -Message "$Key is not currently activated" + $DeactivateProcess = Update-ProcessObject -ProcessObject $DeactivateProcess -ErrorCount 1 + } + } + # Check outcome + if ($Properties.LicenseKey.Count -gt 1) { + $Success = "$($Properties.LicenseKey.Count) licenses were successfully deactivated" + $ErrorCount = $Properties.LicenseKey.Count - $Count + if ($ErrorCount -eq $Properties.LicenseKey.Count) { + $Failure = "None of the licenses could not be deactivated" + } else { + $Failure = "$ErrorCount out of $($Properties.LicenseKey.Count) licenses could not be deactivated" + } + } elseif ($Properties.LicenseKey.Count -eq 1) { + $Success = "$($Properties.LicenseKey.Count) license was successfully deactivated" + $Failure = "License could not be deactivated" + } + if ($Count -eq $Properties.LicenseKey.Count) { + Write-Log -Type "CHECK" -Message $Success + $DeactivateProcess = Update-ProcessObject -ProcessObject $DeactivateProcess -Status "Completed" -Success $true + } elseif ($ErrorCount -eq $Properties.LicenseKey.Count) { + Write-Log -Type "ERROR" -Message $Failure + $DeactivateProcess = Update-ProcessObject -ProcessObject $DeactivateProcess -Status "Failed" -ExitCode 1 } else { - $Failure = "$ErrorCount out of $($Properties.LicenseKey.Count) licenses could not be deactivated" + Write-Log -Type "WARN" -Message $Failure + $DeactivateProcess = Update-ProcessObject -ProcessObject $DeactivateProcess -Status "Completed" } - } elseif ($Properties.LicenseKey.Count -eq 1) { - $Success = "$($Properties.LicenseKey.Count) license was successfully deactivated" - $Failure = "License could not be deactivated" - } - if ($Count -eq $Properties.LicenseKey.Count) { - Write-Log -Type "CHECK" -Message $Success - } elseif ($ErrorCount -eq $Properties.LicenseKey.Count) { - Write-Log -Type "ERROR" -Message $Failure - } else { - Write-Log -Type "WARN" -Message $Failure } } } else { Write-Log -Type "ERROR" -Message "Unable to reach licensing system" Write-Log -Type "WARN" -Message "Skipping license deactivation" + $DeactivateProcess = Update-ProcessObject -ProcessObject $DeactivateProcess -Status "Failed" -ErrorCount 1 -ExitCode 1 } } } + End { + return $DeactivateProcess + } } \ No newline at end of file diff --git a/powershell/Invoke-DownloadAlteryx.ps1 b/powershell/Invoke-DownloadAlteryx.ps1 new file mode 100644 index 0000000..dd8a6fa --- /dev/null +++ b/powershell/Invoke-DownloadAlteryx.ps1 @@ -0,0 +1,246 @@ +function Invoke-DownloadAlteryx { + <# + .SYNOPSIS + Download Alteryx release + + .DESCRIPTION + Download the latest Alteryx release for a specified product + + .NOTES + File name: Invoke-DownloadAlteryx.ps1 + Author: Florian Carrier + Creation date: 2024-09-04 + Last modified: 2024-10-08 + #> + [CmdletBinding ( + SupportsShouldProcess = $true + )] + Param ( + [Parameter ( + Position = 1, + Mandatory = $true, + HelpMessage = "Script properties" + )] + [ValidateNotNullOrEmpty ()] + [System.Collections.Specialized.OrderedDictionary] + $Properties, + [Parameter ( + Position = 2, + Mandatory = $true, + HelpMessage = "Installation properties" + )] + [ValidateNotNullOrEmpty ()] + [System.Collections.Specialized.OrderedDictionary] + $InstallationProperties, + [Parameter ( + HelpMessage = "Non-interactive mode" + )] + [Switch] + $Unattended + ) + Begin { + # Get global preference vrariables + Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState + # Log function call + Write-Log -Type "DEBUG" -Message $MyInvocation.MyCommand.Name + # Process status + $DownloadProcess = New-ProcessObject -Name $MyInvocation.MyCommand.Name + # Product IDs + $Products = [Ordered]@{ + "Designer" = "Alteryx Designer" + "Server" = "Alteryx Server" + } + $ProductID = $Products.$($Properties.Product) + # Alteryx installation registry key + $RegistryKey = "HKLM:HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\SRC\Alteryx" + # License API refresh token + $LicenseAPIPath = Join-Path -Path $Properties.ResDirectory -ChildPath $Properties.LicenseAPIFile + $RefreshToken = (ConvertFrom-SecureString -SecureString (ConvertTo-SecureString -String (Get-Content -Path $LicenseAPIPath)) -AsPlainText) -replace "`r|`n", "" + # Placeholder + $Skip = $false + } + Process { + $DownloadProcess = Update-ProcessObject -ProcessObject $DownloadProcess -Status "Running" + Write-Log -Type "NOTICE" -Message "Starting download process" + # ------------------------------------------------------------------------------ + # * Checks + # ------------------------------------------------------------------------------ + if ($null -eq $RefreshToken) { + Write-Log -Type "ERROR" -Message "The Alteryx license portal API refresh token has not been configured" + if (-Not $Unattended) { + $RefreshToken = Read-Host -Prompt "Enter your Alteryx license portal API refresh token" + } else { + Write-Log -Type "ERROR" -Message "Download process cannot proceed" + $DownloadProcess = Update-ProcessObject -ProcessObject $DownloadProcess -Status "Failed" -ErrorCount 1 -ExitCode 1 + return $DownloadProcess + } + } + if ($null -eq $Properties.LicenseAccountID) { + Write-Log -Type "ERROR" -Message "The AccountID parameter has not been configured" + if (-Not $Unattended) { + $Properties.LicenseAccountID = Read-Host -Prompt "What is the ID of your account in the Alteryx license portal?" + } else { + Write-Log -Type "ERROR" -Message "Download process cannot proceed" + $DownloadProcess = Update-ProcessObject -ProcessObject $DownloadProcess -Status "Failed" -ErrorCount 1 -ExitCode 1 + return $DownloadProcess + } + } + # ------------------------------------------------------------------------------ + # * Fetch latest version + # ------------------------------------------------------------------------------ + # Get license API access token + if ($PSCmdlet.ShouldProcess("License Portal access token", "Refresh")) { + try { + # Catch issues calling the API + $AccessToken = Update-AlteryxLicenseToken -Token $RefreshToken -Type "Access" + Write-Log -Type "DEBUG" -Message $AccessToken + } catch { + Write-Log -Type "ERROR" -Message (Get-PowerShellError) + Write-Log -Type "ERROR" -Message "Cannot connect to License portal API" + $DownloadProcess = Update-ProcessObject -ProcessObject $DownloadProcess -Status "Failed" -ErrorCount 1 -ExitCode 1 + return $DownloadProcess + } + + } + # Check current and target versions + if ($PSCmdlet.ShouldProcess("Latest release version", "Fetch")) { + if (Test-Path -Path $RegistryKey) { + $CurrentVersion = Get-AlteryxVersion + } else { + $CurrentVersion = "0.0" + } + $MajorVersion = [System.String]::Concat([System.Version]::Parse($CurrentVersion).Major , ".", [System.Version]::Parse($CurrentVersion).Minor) + $TargetVersion = [System.String]::Concat([System.Version]::Parse($Properties.Version).Major, ".", [System.Version]::Parse($Properties.Version).Minor) + # Check latest version + Write-Log -Type "INFO" -Message "Retrieve latest release for $ProductID version $TargetVersion" + $Release = Get-AlteryxLatestRelease -AccountID $Properties.LicenseAccountID -Token $AccessToken -ProductID $ProductID -Version $TargetVersion + # Compare versions + if (Compare-Version -Version $Release.Version -Operator "lt" -Reference $CurrentVersion) { + Write-Log -Type "WARN" -Message "The specified version ($($Release.Version)) is lower than the current one ($CurrentVersion)" + if (($Unattended -eq $false) -And (-Not (Confirm-Prompt -Prompt "Do you still want to download $ProductID version $($Release.Version)?"))) { + $Skip = $true + } + } elseif (Compare-Version -Version $Release.Version -Operator "eq" -Reference $CurrentVersion) { + Write-Log -Type "WARN" -Message "The version installed ($CurrentVersion) is the latest version available for $ProductID" + if (($Unattended -eq $false) -And (-Not (Confirm-Prompt -Prompt "Do you still want to download $ProductID version $($Release.Version)?"))) { + $Skip = $true + } + } else { + Write-Log -Type "INFO" -Message "$ProductID version $($Release.Version) is available" + if (-Not $Unattended) { + $Continue = Confirm-Prompt -Prompt "Do you want to download it?" + if (-Not $Continue) { + $Skip = $true + } + } + } + } + # ------------------------------------------------------------------------------ + # * Download + # ------------------------------------------------------------------------------ + # Check if download should proceed + if ($Skip -eq $false) { + $Products = [Ordered]@{ + "Server" = $ProductID + # "PredictiveTools" = "Predictive Tools" # Included within Server + "IntelligenceSuite" = "Alteryx Intelligence Suite" + # "DataPackages" = "Data Packages" # + } + foreach ($Product in $Products.GetEnumerator()) { + if ($InstallationProperties.$($Product.Key) -eq $true) { + $ProductID = $Product.Value + if ($PSCmdlet.ShouldProcess($ProductID, "Download")) { + $DownloadEXE = $true + $FormattedVersion = "version" + if ($ProductID -in ("Alteryx Server", "Alteryx Designer")) { + # Check upgrade step + if (Compare-Version -Version $TargetVersion -Operator "eq" -Reference $MajorVersion) { + # If minor or patch upgrade, download patch + $Release = Get-AlteryxLatestRelease -AccountID $Properties.LicenseAccountID -Token $AccessToken -ProductID $ProductID -Version $TargetVersion -Patch + $FormattedVersion = "patch version" + } + } else { + # Fetch latest release for add-ons + $Release = Get-AlteryxLatestRelease -AccountID $Properties.LicenseAccountID -Token $AccessToken -ProductID $ProductID -Version $TargetVersion + if ($null -eq $Release) { + $DownloadEXE = $false + } + } + $DownloadPath = Join-Path -Path $Properties.SrcDirectory -ChildPath $Release.FileName + # Check if file already exists + if (Test-Path -Path $DownloadPath) { + Write-Log -Type "WARN" -Message "$ProductID installation file already exist in source directory" + Write-Log -Type "DEBUG" -Message $DownloadPath + if ($Unattended -eq $false) { + $DownloadEXE = Confirm-Prompt "Do you want to redownload and overwrite the existing file?" + } else { + $DownloadEXE = $false + } + } + # Additional check for R + if ($ProductID -eq "Predictive Tools") { + if ($Properties.Product -eq "Server") { + Write-Log -Type "INFO" -Message "$ProductID installer is packaged within Server installation file" + } else { + Write-Log -Type "WARN" -Message "$ProductID download is not yet supported" + $DownloadEXE = $false + } + } + # Download process + if ($DownloadEXE -eq $true) { + Write-Log -Type "INFO" -Message "Downloading $($Release.Product) $FormattedVersion $($Release.Version)" + if (Test-Object -Path $Properties.SrcDirectory -NotFound) { + Write-Log -Type "INFO" -Message "Creating source directory $($Properties.SrcDirectory)" + New-Item -Path $DownloadPath -ItemType "Directory" | Out-Null + } + # Download file + try { + Invoke-WebRequest -Uri $Release.URL -OutFile $DownloadPath -UseBasicParsing + } catch { + Write-Log -Type "ERROR" -Message (Get-PowerShellError) + # Remove failed download file + Remove-Item -Path $DownloadPath -Force + } + # Check downloaded file + Write-Log -Type "DEBUG" -Message $DownloadPath + if (Test-Path -Path $DownloadPath) { + Write-Log -Type "CHECK" -Message "$ProductID download completed successfully" + } else { + Write-Log -Type "ERROR" -Message "Download failed" + $DownloadProcess = Update-ProcessObject -ProcessObject $DownloadProcess -Status "Failed" -ErrorCount 1 -ExitCode 1 + } + } else { + Write-Log -Type "WARN" -Message "Skipping download of $ProductID version $($Release.Version)" + } + } + } else { + Write-Log -Type "WARN" -Message "Skipping $ProductID by configuration" + } + } + } else { + Write-Log -Type "WARN" -Message "Skipping download process" + $DownloadProcess = Update-ProcessObject -ProcessObject $DownloadProcess -Status "Cancelled" -Success $true -ExitCode 0 -ErrorCount 0 + } + # ------------------------------------------------------------------------------ + # * Check + # ------------------------------------------------------------------------------ + if ($DownloadProcess.ErrorCount -eq 0) { + Write-Log -Type "CHECK" -Message "Download of Alteryx $($Properties.Product) $Version successfull" + $DownloadProcess = Update-ProcessObject -ProcessObject $DownloadProcess -Status "Completed" -Success $true + } elseif ($DownloadProcess.ErrorCount -eq 4) { + Write-Log -Type "ERROR" -Message "Download failed" + $DownloadProcess = Update-ProcessObject -ProcessObject $DownloadProcess -Status "Failed" -ExitCode 1 + } else { + if ($DownloadProcess.ErrorCount -eq 1) { + $ErrorCount = "one error" + } else { + $ErrorCount = "$($DownloadProcess.ErrorCount) errors" + } + Write-Log -Type "WARN" -Message "Download completed with $ErrorCount" + $DownloadProcess = Update-ProcessObject -ProcessObject $DownloadProcess -Status "Completed" + } + } + End { + return $DownloadProcess + } +} \ No newline at end of file diff --git a/powershell/Invoke-PatchAlteryx.ps1 b/powershell/Invoke-PatchAlteryx.ps1 index 5ecfc19..558815f 100644 --- a/powershell/Invoke-PatchAlteryx.ps1 +++ b/powershell/Invoke-PatchAlteryx.ps1 @@ -10,7 +10,7 @@ function Invoke-PatchAlteryx { File name: Invoke-PatchAlteryx.ps1 Author: Florian Carrier Creation date: 2022-06-29 - Last modified: 2022-06-29 + Last modified: 2024-09-10 #> [CmdletBinding ( SupportsShouldProcess = $true @@ -34,51 +34,102 @@ function Invoke-PatchAlteryx { # Get global preference vrariables Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState # Log function call - Write-Log -Type "DEBUG" -Message $MyInvocation.ScriptName + Write-Log -Type "DEBUG" -Message $MyInvocation.MyCommand.Name + # Process status + $PatchProcess = New-ProcessObject -Name $MyInvocation.MyCommand.Name # Variables $ISOTimeStamp = Get-Date -Format "yyyyMMdd_HHmmss" - $Tags = [Ordered]@{"Version" = $Properties.Version} # Filenames - if ($InstallationProperties.Product -eq "Designer") { - $PatchInstaller = "AlteryxPatchInstall_.exe" + if ($Properties.Product -eq "Designer") { + $PatchPrefix = "AlteryxPatchInstall" } else { - $PatchInstaller = "AlteryxServerPatchInstall_.exe" - } - # Unattended execution arguments - if ($Unattended) { - $Arguments = "/s" - } else { - $Arguments = "" + $PatchPrefix = "AlteryxServerPatchInstall" } } Process { - Write-Log -Type "INFO" -Message "Installation of Alteryx $($InstallationProperties.Product) patch $($Properties.Version)" - if ($InstallationProperties.Product -eq "Designer" -Or $InstallationProperties.Server -eq $true) { - # Update file version number - $PatchFileName = Set-Tags -String $PatchInstaller -Tags (Resolve-Tags -Tags $Tags -Prefix "<" -Suffix ">") - $PatchPath = Join-Path -Path $Properties.SrcDirectory -ChildPath $PatchFileName + $PatchProcess = Update-ProcessObject -ProcessObject $PatchProcess -Status "Running" + Write-Log -Type "INFO" -Message "Installation of Alteryx $($Properties.Product) patch $($Properties.Version)" + # Check products to install + if ($Properties.Product -eq "Designer" -Or $InstallationProperties.Server -eq $true) { + # Generate patch file version number + $PatchVersion = [System.Version]::Parse($Properties.Version).Major.ToString() + "." + [System.Version]::Parse($Properties.Version).Minor.ToString() + "." + [System.Version]::Parse($Properties.Version).Build.ToString() + ".?." + [System.Version]::Parse($Properties.Version).Revision.ToString() + $PatchInstaller = "$($PatchPrefix)_$($PatchVersion).exe" + $PatchPath = Join-Path -Path $Properties.SrcDirectory -ChildPath $PatchInstaller if ($PSCmdlet.ShouldProcess($PatchPath, "Install")) { if (Test-Path -Path $PatchPath) { + # Get actual filepath + $PatchPath = (Resolve-Path -Path $PatchPath).Path + # Stop Alteryx Service + Write-Log -Type "INFO" -Message "Check Alteryx Service status" + if ($PSCmdlet.ShouldProcess("Alteryx Service", "Stop")) { + $Service = "AlteryxService" + if (Test-Service -Name $Service) { + $WindowsService = Get-Service -Name $Service + Write-Log -Type "DEBUG" -Message $Service + $ServiceStatus = $WindowsService.Status + if ($ServiceStatus -eq "Running") { + $StopProcess = Invoke-StopAlteryx -Properties $Properties -Unattended:$Unattended + if ($StopProcess.Success -eq $false) { + $PatchProcess = Update-ProcessObject -ProcessObject $PatchProcess -ErrorCount $StopProcess.ErrorCount + } + } + } else { + Write-Log -Type "ERROR" -Message "Alteryx Service ($Service) could not be found" + $PatchProcess = Update-ProcessObject -ProcessObject $PatchProcess -Status "Failed" -ErrorCount 1 -ExitCode 1 + return $PatchProcess + } + } if ($Properties.InstallAwareLog -eq $true) { $InstallAwareLog = Join-Path -Path $Properties.LogDirectory -ChildPath "${ISOTimeStamp}_${PatchFileName}.log" - $PatchInstall = Install-AlteryxServer -Path $PatchPath -InstallDirectory $Properties.InstallationPath -Log $InstallAwareLog -Language $Properties.Language -AllUsers -Unattended:$Unattended + $PatchInstall = Install-AlteryxServer -Path $PatchPath -InstallDirectory $Properties.InstallationPath -Log $InstallAwareLog -Language $Properties.Language -Version $Properties.Version -AllUsers -Unattended:$Unattended } else { - $PatchInstall = Install-AlteryxServer -Path $PatchPath -InstallDirectory $Properties.InstallationPath -Language $Properties.Language -AllUsers -Unattended:$Unattended + $PatchInstall = Install-AlteryxServer -Path $PatchPath -InstallDirectory $Properties.InstallationPath -Language $Properties.Language -Version $Properties.Version -AllUsers -Unattended:$Unattended } Write-Log -Type "DEBUG" -Message $PatchInstall if ($PatchInstall.ExitCode -eq 0) { - Write-Log -Type "CHECK" -Message "Alteryx $($InstallationProperties.Product) patched successfully" + Write-Log -Type "CHECK" -Message "Alteryx $($Properties.Product) patched successfully" } else { Write-Log -Type "ERROR" -Message "An error occured during the patch installation" -ExitCode $PatchInstall.ExitCode + $PatchProcess = Update-ProcessObject -ProcessObject $PatchProcess -Status "Failed" -ErrorCount 1 -ExitCode 1 + return $PatchProcess } + # TODO check registry for version and installinfo configuration file } else { Write-Log -Type "ERROR" -Message "Path not found $PatchPath" - Write-Log -Type "ERROR" -Message "Alteryx $($InstallationProperties.Product) patch file could not be located" - Write-Log -Type "WARN" -Message "Alteryx patch installation failed" -ExitCode 1 + Write-Log -Type "ERROR" -Message "Alteryx $($Properties.Product) patch file could not be located" + Write-Log -Type "WARN" -Message "Alteryx patch installation failed" + $PatchProcess = Update-ProcessObject -ProcessObject $PatchProcess -Status "Failed" -ErrorCount 1 -ExitCode 1 + return $PatchProcess + } + # Restart service if it was running before + if ($ServiceStatus -eq "Running") { + $StartProcess = Invoke-StartAlteryx -Properties $Properties -Unattended:$Unattended + if ($StartProcess.Success -eq $false) { + $PatchProcess = Update-ProcessObject -ProcessObject $PatchProcess -ErrorCount $StartProcess.ErrorCount + } } } } else { - Write-Log -Type "ERROR" -Message "Designer or Server products must be enabled for a patch upgrade" -ExitCode 0 + Write-Log -Type "ERROR" -Message "Designer or Server products must be enabled for a patch upgrade" + $PatchProcess = Update-ProcessObject -ProcessObject $PatchProcess -Status "Completed" } + # ------------------------------------------------------------------------------ + # * Check + # ------------------------------------------------------------------------------ + if ($PatchProcess.ErrorCount -eq 0) { + Write-Log -Type "CHECK" -Message "Alteryx $($Properties.Product) $($Properties.Version) patched successfully" + $PatchProcess = Update-ProcessObject -ProcessObject $PatchProcess -Status "Completed" -Success $true + } else { + if ($PatchProcess.ErrorCount -eq 1) { + $ErrorCount = "one error" + } else { + $ErrorCount = "$($PatchProcess.ErrorCount) errors" + } + Write-Log -Type "WARN" -Message "Alteryx $($Properties.Product) $($Properties.Version) was patched with $ErrorCount" + $PatchProcess = Update-ProcessObject -ProcessObject $PatchProcess -Status "Completed" + } + } + End { + return $PatchProcess } } \ No newline at end of file diff --git a/powershell/Invoke-PingAlteryx.ps1 b/powershell/Invoke-PingAlteryx.ps1 new file mode 100644 index 0000000..f11050a --- /dev/null +++ b/powershell/Invoke-PingAlteryx.ps1 @@ -0,0 +1,103 @@ +function Invoke-PingAlteryx { + <# + .SYNOPSIS + Ping Alteryx Server + + .DESCRIPTION + Ping Alteryx Server to check if it alive + + .NOTES + File name: Invoke-PingAlteryx.ps1 + Author: Florian Carrier + Creation date: 2024-09-16 + Last modified: 2024-09-18 + #> + [CmdletBinding ( + SupportsShouldProcess = $true + )] + Param ( + [Parameter ( + Position = 1, + Mandatory = $true, + HelpMessage = "Script properties" + )] + [ValidateNotNullOrEmpty ()] + [System.Collections.Specialized.OrderedDictionary] + $Properties, + [Parameter ( + HelpMessage = "Non-interactive mode" + )] + [Switch] + $Unattended + ) + Begin { + # Get global preference vrariables + Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState + # Log function call + Write-Log -Type "DEBUG" -Message $MyInvocation.MyCommand.Name + # Process status + $PingProcess = New-ProcessObject -Name $MyInvocation.MyCommand.Name + # Parameters + $HostName = [System.Net.Dns]::GetHostName() + $ServiceName = "AlteryxService" + } + Process { + $PingProcess = Update-ProcessObject -ProcessObject $PingProcess -Status "Running" + Write-Log -Type "NOTICE" -Message "Pinging Alteryx Server" + # ---------------------------------------------------------------------------- + # * Alteryx service + # ---------------------------------------------------------------------------- + Write-Log -Type "INFO" -Message "Check Alteryx Service status" + if ($PSCmdlet.ShouldProcess("Alteryx Service", "Check")) { + $AlteryxService = Get-Service -Name $ServiceName + Write-Log -Type "DEBUG" -Message $AlteryxService + $ServiceStatus = Format-String -String $AlteryxService.Status -Format "LowerCase" + if ($AlteryxService.Status -eq "Running") { + Write-Log -Type "CHECK" -Message "$ServiceName is $ServiceStatus" + } else { + Write-Log -Type "ERROR" -Message "$ServiceName is $ServiceStatus" + $PingProcess = Update-ProcessObject -ProcessObject $PingProcess -Status "Failed" -ErrorCount 1 -ExitCode 1 + if ($Unattended -eq $false) { + $Continue = Confirm-Prompt -Prompt "Alteryx Service does not appear to be running, do you still want to try to ping the Gallery?" + if ($Continue -eq $false) { + return $PingProcess + } + } else { + return $PingProcess + } + } + } + # ---------------------------------------------------------------------------- + # * Gallery + # ---------------------------------------------------------------------------- + Write-Log -Type "INFO" -Message "Check Alteryx Gallery status" + if ($PSCmdlet.ShouldProcess("Alteryx Gallery", "Ping")) { + if ($Properties.EnableSSL -eq $true) { + $Protocol = "https" + } else { + $Protocol = "http" + } + $GalleryURL = "$($Protocol)://$($HostName)/gallery" + if (Test-HTTPStatus -URI $GalleryURL) { + Write-Log -Type "CHECK" -Message "Alteryx Gallery is accessible" + $PingProcess = Update-ProcessObject -ProcessObject $PingProcess -Status "Completed" -Success $true + } else { + # Try localhost + if (Test-HTTPStatus -URI "$($Protocol)://localhost/gallery") { + Write-Log -Type "CHECK" -Message "Alteryx Gallery is accessible" + Write-Log -Type "WARN" -Message "Consider changing the base address not to use localhost" + $PingProcess = Update-ProcessObject -ProcessObject $PingProcess -Status "Completed" -Success $true + } else { + Write-Log -Type "ERROR" -Message "Alteryx Gallery is not accessible" + $PingProcess = Update-ProcessObject -ProcessObject $PingProcess -Status "Failed" -ErrorCount 1 -ExitCode 1 + } + } + } else { + # WhatIf + $PingProcess = Update-ProcessObject -ProcessObject $PingProcess -Status "Completed" -Success $true + } + } + End { + return $PingProcess + } +} \ No newline at end of file diff --git a/powershell/Invoke-RestartAlteryx.ps1 b/powershell/Invoke-RestartAlteryx.ps1 index e3a8d3d..ce94a9d 100644 --- a/powershell/Invoke-RestartAlteryx.ps1 +++ b/powershell/Invoke-RestartAlteryx.ps1 @@ -10,7 +10,7 @@ function Invoke-RestartAlteryx { File name: Invoke-RestartAlteryx.ps1 Author: Florian Carrier Creation date: 2021-08-27 - Last modified: 2022-04-19 + Last modified: 2024-09-23 #> [CmdletBinding ()] Param ( @@ -32,12 +32,27 @@ function Invoke-RestartAlteryx { # Get global preference vrariables Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState # Log function call - Write-Log -Type "DEBUG" -Message $MyInvocation.ScriptName + Write-Log -Type "DEBUG" -Message $MyInvocation.MyCommand.Name + # Process status + $RestartProcess = New-ProcessObject -Name $MyInvocation.MyCommand.Name } Process { - Write-Log -Type "CHECK" -Message "Restarting Alteryx Service" - Invoke-StopAlteryx -Properties $Properties -Unattended:$Unattended - Invoke-StartAlteryx -Properties $Properties -Unattended:$Unattended - Write-Log -Type "CHECK" -Message "Alteryx Service restart complete" + $RestartProcess = Update-ProcessObject -ProcessObject $RestartProcess -Status "Running" + Write-Log -Type "NOTICE" -Message "Restarting Alteryx Service" + $StopProcess = Invoke-StopAlteryx -Properties $Properties -Unattended:$Unattended + if ($StopProcess.Success) { + $StartProcess = Invoke-StartAlteryx -Properties $Properties -Unattended:$Unattended + if ($StartProcess.Success) { + Write-Log -Type "CHECK" -Message "Alteryx Service restart process complete" + $RestartProcess = Update-ProcessObject -ProcessObject $RestartProcess -Status "Completed" -Success $true + } else { + $RestartProcess = Update-ProcessObject -ProcessObject $RestartProcess -Status $StopProcess.Status -ErrorCount $StartProcess.ErrorCount -ExitCode $StartProcess.ExitCode + } + } else { + $RestartProcess = Update-ProcessObject -ProcessObject $RestartProcess -Status $StartProcess.Status -ErrorCount $StopProcess.ErrorCount -ExitCode $StopProcess.ExitCode + } + } + End { + return $RestartProcess } } \ No newline at end of file diff --git a/powershell/Invoke-RestoreAlteryx.ps1 b/powershell/Invoke-RestoreAlteryx.ps1 index af91c12..6343f88 100644 --- a/powershell/Invoke-RestoreAlteryx.ps1 +++ b/powershell/Invoke-RestoreAlteryx.ps1 @@ -10,13 +10,13 @@ function Invoke-RestoreAlteryx { File name: Invoke-RestoreAlteryx.ps1 Author: Florian Carrier Creation date: 2021-08-26 - Last modified: 2022-04-19 + Last modified: 2024-09-11 Comment: User configuration files are out of scope of this procedure: - %APPDATA%\Alteryx\Engine\UserConnections.xml - %APPDATA%\Alteryx\Engine\UserAlias.xml .LINK - https://help.alteryx.com/20213/server/server-host-recovery-guide + https://help.alteryx.com/current/en/server/install/server-host-recovery-guide.html #> [CmdletBinding ( SupportsShouldProcess = $true @@ -40,10 +40,13 @@ function Invoke-RestoreAlteryx { # Get global preference vrariables Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState # Log function call - Write-Log -Type "DEBUG" -Message $MyInvocation.ScriptName + Write-Log -Type "DEBUG" -Message $MyInvocation.MyCommand.Name + # Process status + $RestoreProcess = New-ProcessObject -Name $MyInvocation.MyCommand.Name # Variables $ServicePath = Join-Path -Path $Properties.InstallationPath -ChildPath "bin\AlteryxService.exe" $Staging = $false + $MajorVersion = [System.String]::Concat([System.Version]::Parse($Properties.Version).Major, ".", [System.Version]::Parse($Properties.Version).Minor) # Restore options $Restore = [Ordered]@{ "Database" = $true @@ -67,9 +70,12 @@ function Invoke-RestoreAlteryx { } } Process { - Write-Log -Type "CHECK" -Message "Start $RestoreType restore of Alteryx Server" + $RestoreProcess = Update-ProcessObject -ProcessObject $RestoreProcess -Status "Running" + Write-Log -Type "NOTICE" -Message "Start $RestoreType restore of Alteryx Server" + # ---------------------------------------------------------------------------- + # * Checks + # ---------------------------------------------------------------------------- if ($PSCmdlet.ShouldProcess("Alteryx Service", "Stop")) { - # ---------------------------------------------------------------------------- # Check Alteryx service status Write-Log -Type "INFO" -Message "Check Alteryx Service status" $Service = "AlteryxService" @@ -81,35 +87,67 @@ function Invoke-RestoreAlteryx { Invoke-StopAlteryx -Properties $Properties -Unattended:$Unattended } } else { - Write-Log -Type "ERROR" -Message "Alteryx Service ($Service) could not be found" -ExitCode 1 + Write-Log -Type "ERROR" -Message "Alteryx Service ($Service) could not be found" + $RestoreProcess = Update-ProcessObject -ProcessObject $RestoreProcess -Status "Failed" -ErrorCode 1 -ExitCode 1 + return $RestoreProcess } } - # ---------------------------------------------------------------------------- # Check source backup path if ($PSCmdlet.ShouldProcess("Backup files", "Retrieve")) { + # Check if custom backup path is specified if ($null -eq (Get-KeyValue -Hashtable $Properties -Key "BackupPath" -Silent)) { $SourcePath = $Properties.BackupDirectory } else { $SourcePath = $Properties.BackupPath } + # Look for backup file if (Test-Object -Path $SourcePath) { - if ($SourcePath -is [System.IO.FileInfo]) { - if ([System.IO.Path]::GetExtension($SourcePath) -eq ".zip") { + $Source = Get-Item -Path $SourcePath + # Check if source is a file + if ($Source.PSIsContainer -eq $false) { + if ((Format-String -String $Source.Extension -Format "LowerCase") -eq ".zip") { # Extract archive file Write-Log -Type "INFO" -Message "Extract backup file contents" $BackupPath = Join-Path -Path $Properties.TempDirectory -ChildPath ([System.IO.Path]::GetFileNameWithoutExtension($SourcePath)) Expand-Archive -Path $SourcePath -DestinationPath $BackupPath -Force -WhatIf:$WhatIfPreference $Staging = $true } else { - Write-Log -Type "ERROR" -Message "Only ZIP or uncompressed files are supported" -ExitCode 1 + Write-Log -Type "ERROR" -Message "Only ZIP files are supported" + $RestoreProcess = Update-ProcessObject -ProcessObject $RestoreProcess -Status "Failed" -ErrorCode 1 -ExitCode 1 + return $RestoreProcess } } else { # Select most recent backup in the directory Write-Log -Type "WARN" -Message "No backup file was specified" + # Ask user confirmation on most recent file + if ($Unattended -eq $false) { + $Confirmation = Confirm-Prompt -Prompt "Do you want to fetch the latest backup file?" + if ($Confirmation -eq $false) { + Write-Log -Type "WARN" -Message "Restore operation cancelled by user" + $RestoreProcess = Update-ProcessObject -ProcessObject $RestoreProcess -Status "Cancelled" + return $RestoreProcess + } + } Write-Log -Type "DEBUG" -Message $SourcePath - Write-Log -Type "INFO" -Message "Retrieving most recent backup" - $BackupFile = (Get-Object -Path $SourcePath -ChildItem -Type "File" -Filter "*.zip" | Sort-Object -Descending -Property "LastWriteTime" | Select-Object -First 1).FullName + Write-Log -Type "INFO" -Message "Retrieving most recent backup matching major version $MajorVersion" + $Pattern = ".+Alteryx_$($Properties.Product)_$($MajorVersion).+" + $BackupFile = (Get-Object -Path $SourcePath -ChildItem -Type "File" -Filter "*.zip" | Where-Object -Property "Name" -Match $Pattern | Sort-Object -Descending -Property "LastWriteTime" | Select-Object -First 1).FullName Write-Log -Type "DEBUG" -Message $BackupFile + if ($null -ne $BackupFile) { + # Ask user confirmation on backup file + if ($Unattended -eq $false) { + $Confirmation = Confirm-Prompt -Prompt "Do you want to restore backup from $($BackupFile.FileName)?" + if ($Confirmation -eq $false) { + Write-Log -Type "WARN" -Message "Restore operation cancelled by user" + $RestoreProcess = Update-ProcessObject -ProcessObject $RestoreProcess -Status "Cancelled" + return $RestoreProcess + } + } + } else { + Write-Log -Type "ERROR" -Message "No suitable database back-up file could be found" + $RestoreProcess = Update-ProcessObject -ProcessObject $RestoreProcess -Status "Failed" -ErrorCount 1 -ExitCode 1 + return $RestoreProcess + } # Extract archive file Write-Log -Type "INFO" -Message "Extract backup file contents" $BackupPath = Join-Path -Path $Properties.TempDirectory -ChildPath ([System.IO.Path]::GetFileNameWithoutExtension($BackupFile)) @@ -117,11 +155,14 @@ function Invoke-RestoreAlteryx { $Staging = $true } } else { - Write-Log -Type "ERROR" -Message "No database backup could be found" -ExitCode 1 + Write-Log -Type "ERROR" -Message "No database back-up file could be found" + $RestoreProcess = Update-ProcessObject -ProcessObject $RestoreProcess -Status "Failed" -ErrorCount 1 -ExitCode 1 + return $RestoreProcess } } # ---------------------------------------------------------------------------- - # Restore configuration files + # * Restore configuration files + # ---------------------------------------------------------------------------- if ($Restore.Configuration -eq $true) { Write-Log -Type "INFO" -Message "Restore configuration files" # TODO restore extra configuration files @@ -139,7 +180,8 @@ function Invoke-RestoreAlteryx { } } # ---------------------------------------------------------------------------- - # Update configuration + # * Update configuration + # ---------------------------------------------------------------------------- Write-Log -Type "INFO" -Message "Updating configuration" if ($PSCmdlet.ShouldProcess("RunTimeSetting.xml", "Update")) { $RunTimeSettingsXML = New-Object -TypeName "System.XML.XMLDocument" @@ -215,7 +257,8 @@ function Invoke-RestoreAlteryx { } } # ---------------------------------------------------------------------------- - # (Re)Set controller token + # * (Re)Set controller token + # ---------------------------------------------------------------------------- if ($Restore.Token -eq $true) { Write-Log -Type "INFO" -Message "Remove encrypted controller token" if ($PSCmdlet.ShouldProcess("Encrypted controller token", "Remove")) { @@ -228,6 +271,7 @@ function Invoke-RestoreAlteryx { $ControllerSettingsXML.Save($ConfigurationFiles.RunTimeSettings) } else { Write-Log -Type "WARN" -Message "Encrypted controller token could not be found" + $RestoreProcess = Update-ProcessObject -ProcessObject $RestoreProcess -ErrorCount 1 } } Write-Log -Type "INFO" -Message "Restore controller token" @@ -239,17 +283,24 @@ function Invoke-RestoreAlteryx { if ($SetToken -match "failed") { Write-Log -Type "ERROR" -Message $SetToken Write-Log -Type "ERROR" -Message "Controller token update failed" + $RestoreProcess = Update-ProcessObject -ProcessObject $RestoreProcess -ErrorCount 1 } else { # Ignore successfull empty output if ($SetToken -ne "") { Write-Log -Type "DEBUG" -Message $SetToken } } + } else { + Write-Log -Type "DEBUG" -Message $TokenFile + Write-Log -Type "ERROR" -Message "No controller token backup file could not be found" + $RestoreProcess = Update-ProcessObject -ProcessObject $RestoreProcess -ErrorCount 1 + Write-Log -Type "WARN" -Message "Skipping controller token restore" } } } # ---------------------------------------------------------------------------- - # Set Run-as user + # * Set Run-as user + # ---------------------------------------------------------------------------- if ($Restore.RunAsUser -eq $true) { if ($PSCmdlet.ShouldProcess("Run-as user credentials", "Restore")) { # TODO @@ -257,7 +308,8 @@ function Invoke-RestoreAlteryx { } } # ---------------------------------------------------------------------------- - # Set SMTP password + # * Set SMTP password + # ---------------------------------------------------------------------------- if ($Restore.SMTPPassword -eq $true) { if ($PSCmdlet.ShouldProcess("SMTP password", "Restore")) { # TODO @@ -265,7 +317,8 @@ function Invoke-RestoreAlteryx { } } # ---------------------------------------------------------------------------- - # Reset storage key + # * Reset storage key + # ---------------------------------------------------------------------------- if ($Restore.Configuration -eq $true) { Write-Log -Type "INFO" -Message "Restore storage key" if ($PSCmdlet.ShouldProcess("Storage Key", "Restore")) { @@ -285,11 +338,13 @@ function Invoke-RestoreAlteryx { $StorageSettingsXML.Save($ConfigurationFiles.RunTimeSettings) } else { Write-Log -Type "WARN" -Message "RunTimeSettings.xml backup configuration file could not be located" + $RestoreProcess = Update-ProcessObject -ProcessObject $RestoreProcess -ErrorCount 1 } } } # ---------------------------------------------------------------------------- - # Restore database + # * Restore database + # ---------------------------------------------------------------------------- if ($Restore.Database -eq $true) { Write-Log -Type "INFO" -Message "Restore MongoDB database from backup" if ($PSCmdlet.ShouldProcess("MongoDB", "Restore")) { @@ -306,13 +361,15 @@ function Invoke-RestoreAlteryx { $DatabaseRestore = Restore-AlteryxDatabase -SourcePath $MongoDBPath -TargetPath $TargetPath -ServicePath $ServicePath if ($DatabaseRestore -match "failed") { Write-Log -Type "ERROR" -Message $DatabaseRestore - Write-Log -Type "ERROR" -Message "Database restore failed" -ExitCode 1 + Write-Log -Type "ERROR" -Message "Database restore failed" + $RestoreProcess = Update-ProcessObject -ProcessObject $RestoreProcess -Status "Failed" -ErrorCount 1 -ExitCode 1 } else { Write-Log -Type "DEBUG" -Message $DatabaseRestore } } else { Write-Log -Type "ERROR" -Message "User-managed MongoDB is not supported" Write-Log -Type "WARN" -Message "Skipping database restore" + $RestoreProcess = Update-ProcessObject -ProcessObject $RestoreProcess -Status "Failed" -ErrorCount 1 -ExitCode 1 } } } @@ -328,9 +385,14 @@ function Invoke-RestoreAlteryx { Invoke-StartAlteryx -Properties $Properties -Unattended:$Unattended } } + if (($RestoreProcess.ErrorCount -eq 0) -And ($RestoreProcess.ExitCode -eq 0)) { + Write-Log -Type "CHECK" -Message "Alteryx Server $RestoreType restore complete" + $RestoreProcess = Update-ProcessObject -ProcessObject $RestoreProcess -Status "Completed" -Success $true + } else { + Write-Log -Type "ERROR" -Message "Alteryx Server $RestoreType restore could not be completed" + } } End { - # If no error occured - Write-Log -Type "CHECK" -Message "Alteryx Server $RestoreType restore complete" + return $RestoreProcess } } \ No newline at end of file diff --git a/powershell/Invoke-RollbackAlteryx.ps1 b/powershell/Invoke-RollbackAlteryx.ps1 new file mode 100644 index 0000000..8c1bb1e --- /dev/null +++ b/powershell/Invoke-RollbackAlteryx.ps1 @@ -0,0 +1,141 @@ +function Invoke-RollbackAlteryx { + <# + .SYNOPSIS + Rollback Alteryx + + .DESCRIPTION + Rollback Alteryx Server to a previous (stable) known state + + .NOTES + File name: Invoke-RollbackAlteryx.ps1 + Author: Florian Carrier + Creation date: 2024-09-23 + Last modified: 2024-09-23 + #> + [CmdletBinding ( + SupportsShouldProcess = $true + )] + Param ( + [Parameter ( + Position = 1, + Mandatory = $true, + HelpMessage = "Properties" + )] + [ValidateNotNullOrEmpty ()] + [System.Collections.Specialized.OrderedDictionary] + $Properties, + [Parameter ( + Position = 2, + Mandatory = $true, + HelpMessage = "Installation properties" + )] + [ValidateNotNullOrEmpty ()] + [System.Collections.Specialized.OrderedDictionary] + $InstallationProperties, + [Parameter ( + HelpMessage = "Non-interactive mode" + )] + [Switch] + $Unattended + ) + Begin { + # Get global preference vrariables + Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState + # Log function call + Write-Log -Type "DEBUG" -Message $MyInvocation.MyCommand.Name + # Process status + $RollbackProcess = New-ProcessObject -Name $MyInvocation.MyCommand.Name + # Clear error pipeline + $Error.Clear() + # Retrieve current version + Write-Log -Type "DEBUG" -Object "Retrieving current version" + if ($PSCmdlet.ShouldProcess("Alteryx version", "Retrieve")) { + # Check registry for installation path to avoid issues if directory has changed + $AlteryxVersion = Get-AlteryxVersion + Write-Log -Type "DEBUG" -Object $AlteryxVersion + $RollbackVersion = Select-String -InputObject $AlteryxVersion -Pattern "\d+\.\d+.\d+(.\d+)?" | ForEach-Object { $PSItem.Matches.Value } + } + } + Process { + $RollbackProcess = Update-ProcessObject -ProcessObject $RollbackProcess -Status "Running" + Write-Log -Type "NOTICE" -Object "Starting Alteryx Server rollback from $RollbackVersion to $($Properties.Version)" + # ------------------------------------------------------------------------------ + # * Checks + # ------------------------------------------------------------------------------ + if ($Unattended -eq $false) { + # Ask for confirmation to uninstall + $ConfirmUninstall = Confirm-Prompt -Prompt "Are you sure that you want to rollback to version $($Properties.Version)?" + if ($ConfirmUninstall -eq $false) { + Write-Log -Type "WARN" -Message "Cancelling rollback" + $Uninstallprocess = Update-ProcessObject -ProcessObject $Uninstallprocess -Status "Cancelled" + return $Uninstallprocess + } + } + # ------------------------------------------------------------------------------ + # * Uninstall + # ------------------------------------------------------------------------------ + # + # TODO back error + $UninstallProperties = Copy-OrderedHashtable -Hashtable $Properties -Deep + $UninstallProperties.Version = $RollbackVersion + # Unistall existing Alteryx version + $UninstallProcess = Uninstall-Alteryx -Properties $UninstallProperties -InstallationProperties $InstallationProperties -Unattended:$Unattended + if ($UninstallProcess.Success -eq $false) { + Write-Log -Type "ERROR" -Message "Rollback process failed" + $RollbackProcess = Update-ProcessObject -ProcessObject $RollbackProcess -Status "Failed" -ErrorCount $UninstallProcess.ErrorCount -ExitCode 1 + return $RollbackProcess + } + # ------------------------------------------------------------------------------ + # * Reinstall + # ------------------------------------------------------------------------------ + # Reinstall previous Alteryx version + $InstallProcess = Install-Alteryx -Properties $Properties -InstallationProperties $InstallationProperties -Unattended:$Unattended + if ($InstallProcess.Success -eq $false -And $InstallProcess.ExitCode -eq 1) { + Write-Log -Type "ERROR" -Message "Rollback process failed" + $RollbackProcess = Update-ProcessObject -ProcessObject $RollbackProcess -Status "Failed" -ErrorCount $InstallProcess.ErrorCount -ExitCode 1 + return $RollbackProcess + } + # ------------------------------------------------------------------------------ + # * Restore + # ------------------------------------------------------------------------------ + # Restore backup + $RestoreProcess = Invoke-RestoreAlteryx -Properties $Properties -Unattended:$Unattended + if ($RestoreProcess.Success -eq $false) { + Write-Log -Type "ERROR" -Message "Rollback process failed" + $RollbackProcess = Update-ProcessObject -ProcessObject $RollbackProcess -Status "Failed" -ErrorCount $RestoreProcess.ErrorCount -ExitCode 1 + return $RollbackProcess + } else { + Write-Log -Type "CHECK" -Object "Alteryx Server rollback completed successfully" + Write-Log -Type "WARN" -Message "Check the logs to troubleshoot issue with upgrade" + Update-ProcessObject -ProcessObject $RollbackProcess -Status "Failed" -ExitCode 1 + } + # ------------------------------------------------------------------------------ + # * Restart + # ------------------------------------------------------------------------------ + $StartProcess = Invoke-StartAlteryx -Properties $Properties -Unattended:$Unattended + if ($StartProcess.Success) { + Write-Log -Type "CHECK" -Message "Alteryx Service restart process complete" + $RollbackProcess = Update-ProcessObject -ProcessObject $RollbackProcess -Status "Completed" -Success $true + } else { + $RollbackProcess = Update-ProcessObject -ProcessObject $RollbackProcess -Status $StopProcess.Status -ErrorCount $StartProcess.ErrorCount -ExitCode $StartProcess.ExitCode + } + # ------------------------------------------------------------------------------ + # * Checks + # ------------------------------------------------------------------------------ + if ($RollbackProcess.ErrorCount -eq 0) { + Write-Log -Type "CHECK" -Message "Alteryx $($Properties.Product) $($Properties.Version) installed successfully" + $RollbackProcess = Update-ProcessObject -ProcessObject $RollbackProcess -Status "Completed" -Success $true + } else { + if ($RollbackProcess.ErrorCount -eq 1) { + $ErrorCount = "one error" + } else { + $ErrorCount = "$($RollbackProcess.ErrorCount) errors" + } + Write-Log -Type "WARN" -Message "Alteryx $($Properties.Product) $($Properties.Version) was rolled back with $ErrorCount" + $RollbackProcess = Update-ProcessObject -ProcessObject $RollbackProcess -Status "Completed" + } + } + End { + return $RollbackProcess + } +} \ No newline at end of file diff --git a/powershell/Invoke-SetupScript.ps1 b/powershell/Invoke-SetupScript.ps1 new file mode 100644 index 0000000..c8024a2 --- /dev/null +++ b/powershell/Invoke-SetupScript.ps1 @@ -0,0 +1,294 @@ +function Invoke-SetupScript { + <# + .SYNOPSIS + Set-up Alteryx deploy utility + + .DESCRIPTION + Set-up wizard to configure the required parameters of the alteryx-deploy utility + + .NOTES + File name: Invoke-SetupScript.ps1 + Author: Florian Carrier + Creation date: 2022-05-03 + Last modified: 2024-10-08 + #> + [CmdletBinding ( + SupportsShouldProcess = $true + )] + Param ( + [Parameter ( + Position = 1, + Mandatory = $true, + HelpMessage = "Script properties" + )] + [ValidateNotNullOrEmpty ()] + [System.Collections.Specialized.OrderedDictionary] + $Properties, + [Parameter ( + Position = 2, + Mandatory = $true, + HelpMessage = "Default script properties" + )] + [ValidateNotNullOrEmpty ()] + [System.Collections.Specialized.OrderedDictionary] + $ScriptProperties + ) + Begin { + # Get global preference vrariables + Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState + # Log function call + Write-Log -Type "DEBUG" -Message $MyInvocation.MyCommand.Name + # Process status + $SetupProcess = New-ProcessObject -Name $MyInvocation.MyCommand.Name + # Parameters + $ConfDirectory = $ScriptProperties.ConfDirectory + $DefaultPath = Join-Path -Path $ConfDirectory -ChildPath $ScriptProperties.DefaultProperties + $CustomPath = Join-Path -Path $ConfDirectory -ChildPath $ScriptProperties.CustomProperties + $CustomHeader = "# ------------------------------------------------------------------------------ +# Add your custom configuration here +# e.g. TempDirectory = D:\Temp +# ------------------------------------------------------------------------------ +" + } + Process { + $SetupProcess = Update-ProcessObject -ProcessObject $SetupProcess -Status "Running" + Write-Log -Type "NOTICE" -Message "Setting up script" + # ------------------------------------------------------------------------------ + # * Configure script parameters + # ------------------------------------------------------------------------------ + Write-Log -Type "INFO" -Message "Configuring script parameters" + if ($PSCmdlet.ShouldProcess("Script parameters", "Configure")) { + $ConfigureParameters = $true + # Check if custom configuration ahs already been set + if (Test-Path -Path $CustomPath) { + $CustomConfig = Get-Content -Path $CustomPath -Raw + if ($CustomConfig -ne $CustomHeader) { + $ConfigureParameters = Confirm-Prompt -Prompt "Do you want to overwrite the existing configuration?" + } + } + if ($ConfigureParameters -eq $true) { + Write-Log -Type "INFO" -Message "Leave blank to keep default value" + # Fetch default configuration + $DefaultConfiguration = Get-Properties -Path $DefaultPath -Metadata + $CustomConfiguration = New-Object -TypeName "System.Collections.Specialized.OrderedDictionary" + # Define parameters to exclude + $ExclusionList = ($($DefaultConfiguration.GetEnumerator()).Value | Where-Object { $PSItem.Section -eq "Checks" }).Value + # Loop through parameters + foreach ($Property in $DefaultConfiguration.GetEnumerator()) { + # Exclude reserved parameters + if ($($Property.Value).Section -notin ("SSL", "Checks") -And (-Not $ExclusionList.Contains($Property.Name))) { + $ValuePrompt = [System.String]::Concat($($Property.Value).Description, " (", $Property.Name, ")") + $DefaultValue = $($Property.Value).Value + # Check if a default value exists to display back to user + if ($DefaultValue.Trim() -notin @($null, "")) { + $ValuePrompt = "$ValuePrompt [default value: $DefaultValue]" + } + # Prompt user for input + $NewValue = (Read-Host -Prompt $ValuePrompt).Trim() + if ($NewValue -notin @($null, "")) { + $NewProperty = [Ordered]@{ + "Value" = $NewValue + "Description" = $($Property.Value).Description + "Section" = $($Property.Value).Section + } + $CustomConfiguration.Add($Property.Name, $NewProperty) + Write-Log -Type "DEBUG" -Message $CustomConfiguration[$Property.Name] + } + } + } + # Generate custom configuration file + Write-Log -Type "DEBUG" -Message $CustomConfiguration + $CustomProperties = $CustomHeader + if ($CustomConfiguration.Count -ge 1) { + foreach ($CustomProperty in $CustomConfiguration.GetEnumerator()) { + $Name = $CustomProperty.Name + $Value = $($CustomProperty.Value).Value + $Description = $($CustomProperty.Value).Description + $CustomProperties += [System.String]::Concat("`n", "# $Description", "`n", $Name, " = ", "$Value") + } + } + # Save back to file + try { + Write-Log -Type "DEBUG" -Message $CustomPath + Set-Content -Path $CustomPath -Value $CustomProperties.Trim() -Force + } catch { + Write-Log -Type "ERROR" -Message (Get-PowerShellError) + Write-Log -Type "ERROR" -Message "Custom configuration could not be saved" + $SetupProcess = Update-ProcessObject -ProcessObject $SetupProcess -ErrorCount 1 + } + } + # Reload properties + $Properties = Get-Properties -Path $DefaultPath -CustomPath $CustomPath + } + # ------------------------------------------------------------------------------ + # * Configure license API token + # ------------------------------------------------------------------------------ + Write-Log -Type "INFO" -Message "Configuring License Portal API refresh token" + if ($PSCmdlet.ShouldProcess("License Portal API refresh token", "Configure")) { + $LicenseAPIPath = Join-Path -Path "$PSScriptRoot/.." -ChildPath "$($Properties.ResDirectory)/$($Properties.LicenseAPIFile)" + $LicenseAPIPrompt = "License Portal API refresh token" + $ConfigureLicenseAPI = $true + if (Test-Path -Path $LicenseAPIPath) { + try { + $RefreshAPIToken = ConvertFrom-SecureString -SecureString (ConvertTo-SecureString -String (Get-Content -Path $LicenseAPIPath)) -AsPlainText + if ($RefreshAPIToken -notin ($null, "")) { + Write-Log -Type "WARN" -Message "License Portal API refresh token has already been configured" + Write-Log -Type "DEBUG" -Message $RefreshAPIToken + $ConfigureLicenseAPI = Confirm-Prompt -Prompt "Do you want to reconfigure the License Portal API refresh token?" + } + } + catch { + Write-Log -Type "DEBUG" -Message (Get-PowerShellError) + Write-Log -Type "WARN" -Message "Failed to read current License Portal API token" + } + } + if ($ConfigureLicenseAPI) { + $LicenseAPIToken = (Read-Host -Prompt $LicenseAPIPrompt).Trim() + $EncryptedLicenseAPIToken = ConvertFrom-SecureString -SecureString (ConvertTo-SecureString -String $LicenseAPIToken.ToString() -AsPlainText -Force) + # Write-Log -Type "DEBUG" -Message $EncryptedLicenseAPIToken + Write-Log -Type "DEBUG" -Message $LicenseAPIPath + Set-Content -Path $LicenseAPIPath -Value $EncryptedLicenseAPIToken -NoNewline -Force + if (Test-Path -Path $LicenseAPIPath) { + Write-Log -Type "CHECK" -Message "License Portal API refresh token saved successfully" + } else { + Write-Log -Type "ERROR" -Message (Get-PowerShellError) + Write-Log -Type "ERROR" -Message "License Portal API refresh token could not be saved" + $SetupProcess = Update-ProcessObject -ProcessObject $SetupProcess -ErrorCount 1 + } + } else { + Write-Log -Type "WARN" -Message "Skipping License Portal API refresh token configuration" + } + } + # # ------------------------------------------------------------------------------ + # * Configure Server API keys + # ------------------------------------------------------------------------------ + Write-Log -Type "INFO" -Message "Configuring Server API keys" + if ($PSCmdlet.ShouldProcess("Server API keys", "Configure")) { + $ServerAPIPath = Join-Path -Path "$PSScriptRoot/.." -ChildPath "$($Properties.ResDirectory)/$($Properties.ServerAdminAPI)" + $APIKeyPrompt = "Admin Server API key" + $APISecretPrompt = "Admin Server API secret" + $ConfigureServerAPI = $true + # Check if API keys have already been saved + if (Test-Path -Path $ServerAPIPath) { + $APIKeys = Get-Content -Path $ServerAPIPath | ConvertFrom-JSON + if ($APIKeys -notin ($null, "")) { + Write-Log -Type "WARN" -Message "Server API keys have already been configured" + # Write-Log -Type "DEBUG" -Message $APIKeys + $ConfigureServerAPI = Confirm-Prompt -Prompt "Do you want to reconfigure Server API keys?" + if ($ConfigureServerAPI) { + $APIKeyPrompt = "$APIKeyPrompt (current $($APIKeys.Key))" + $APISecretPrompt = "$APISecretPrompt (current $($APIKeys.Secret))" + } + } + } else { + $ConfigureServerAPI = Confirm-Prompt -Prompt "Do you want to configure Server API keys?" + } + if ($ConfigureServerAPI) { + $ServerAPIKey = (Read-Host -Prompt $APIKeyPrompt).Trim() + $ServerAPISecret = (Read-Host -Prompt $APISecretPrompt).Trim() + $ServerAPIToken = [Ordered]@{ + "key" = ConvertFrom-SecureString -SecureString (ConvertTo-SecureString -String $ServerAPIKey -AsPlainText -Force) + "secret" = ConvertFrom-SecureString -SecureString (ConvertTo-SecureString -String $ServerAPISecret -AsPlainText -Force) + } | ConvertTo-JSON + Write-Log -Type "DEBUG" -Message $ServerAPIToken + Write-Log -Type "DEBUG" -Message $ServerAPIPath + Set-Content -Path $ServerAPIPath -Value $ServerAPIToken -Force + if (Test-Path -Path $ServerAPIPath) { + Write-Log -Type "CHECK" -Message "Server API keys saved successfully" + } else { + Write-Log -Type "ERROR" -Message (Get-PowerShellError) + Write-Log -Type "ERROR" -Message "Server API keys could not be saved" + $SetupProcess = Update-ProcessObject -ProcessObject $SetupProcess -ErrorCount 1 + } + } else { + Write-Log -Type "WARN" -Message "Skipping Server API keys configuration" + } + } + # ------------------------------------------------------------------------------ + # * Configure installation properties + # ------------------------------------------------------------------------------ + Write-Log -Type "INFO" -Message "Configuring installation properties" + if ($PSCmdlet.ShouldProcess("Installation properties", "Configure")) { + $InstallationPropertiesPath = Join-Path -Path $ConfDirectory -ChildPath $Properties.InstallationOptions + $InstallationProperties = Get-Properties -Path $InstallationPropertiesPath + $NewInstallationProperties = "[Installation]" + foreach ($InstallationProperty in $InstallationProperties.GetEnumerator()) { + $Product = [regex]::Replace($InstallationProperty.Name, '([a-z])([A-Z])', '$1 $2') + if (Confirm-Prompt -Prompt "Install $($Product)?") { + $Newvalue = "true" + } else { + $NewValue = "false" + } + $NewInstallationProperties += [System.String]::Concat("`n", $InstallationProperty.Name, " = ", $NewValue) + } + try { + Write-Log -Type "DEBUG" -Message $NewInstallationProperties + Set-Content -Path $InstallationPropertiesPath -Value $NewInstallationProperties -Force + } catch { + Write-Log -Type "ERROR" -Message (Get-PowerShellError) + Write-Log -Type "ERROR" -Message "Installation properties could not be saved" + $SetupProcess = Update-ProcessObject -ProcessObject $SetupProcess -ErrorCount 1 + } + } + # ------------------------------------------------------------------------------ + # * Configure license file + # ------------------------------------------------------------------------------ + Write-Log -Type "INFO" -Message "Configuring license file" + if ($PSCmdlet.ShouldProcess("License file", "Configure")) { + $LicenseFilePath = Join-Path -Path "$PSScriptRoot/.." -ChildPath "$($Properties.ResDirectory)/$($Properties.LicenseFile)" + $ConfigureLicenseFile = $true + if (Test-Path -Path $LicenseFilePath) { + $LicenseKeys = ConvertFrom-SecureString -SecureString (ConvertTo-SecureString -String (Get-Content -Path $LicenseFilePath)) -AsPlainText + if ($LicenseKeys -notin ($null, "")) { + Write-Log -Type "WARN" -Message "License keys have already been configured" + Write-Log -Type "DEBUG" -Message $LicenseKeys + $ConfigureLicenseFile = Confirm-Prompt -Prompt "Do you want to overwrite the existing license keys?" + } + } + if ($ConfigureLicenseFile) { + $LicenseKeys = (Read-Host -Prompt "Enter Alteryx license key(s)").Trim() + try { + $EncryptedLicenseKeys = ConvertFrom-SecureString -SecureString (ConvertTo-SecureString -String $LicenseKeys -AsPlainText -Force) + Write-Log -Type "DEBUG" -Message $EncryptedLicenseKeys + Write-Log -Type "DEBUG" $LicenseFilePath + Set-Content -Path $LicenseFilePath -Value $EncryptedLicenseKeys -Force + } catch { + Write-Log -Type "ERROR" -Message (Get-PowerShellError) + Write-Log -Type "ERROR" -Message "License file could not be saved" + $SetupProcess = Update-ProcessObject -ProcessObject $SetupProcess -ErrorCount 1 + } + } + } + # ------------------------------------------------------------------------------ + # * Configure SSL/TLS + # ------------------------------------------------------------------------------ + # Write-Log -Type "INFO" -Message "Configuring SSL/TLS" + # TODO Check if certificate if provided + # TODO Generate self-signed cetrificate + # TODO Enable SSL configuration + # ------------------------------------------------------------------------------ + # * Configure SMTP + # ------------------------------------------------------------------------------ + # Write-Log -Type "INFO" -Message "Configuring SMTP settings" + # TODO Configure SMTP settings + # ------------------------------------------------------------------------------ + # * Checks + # ------------------------------------------------------------------------------ + if ($SetupProcess.ErrorCount -eq 5) { + # If all configuration failed + Write-Log -Type "ERROR" -Message "Set-up wizard failed" + $SetupProcess = Update-ProcessObject -ProcessObject $SetupProcess -Status "Failed" -ExitCode 1 + } elseif ($SetupProcess.ErrorCount -gt 0) { + # If only partial failure + Write-Log -Type "WARN" -Message "Set-up wizard completed with errors" + $SetupProcess = Update-ProcessObject -ProcessObject $SetupProcess -Status "Completed" + } else { + # Otherwise success + Write-Log -Type "CHECK" -Message "Set-up wizard complete" + $SetupProcess = Update-ProcessObject -ProcessObject $SetupProcess -Status "Completed" -Success $true + } + } + End { + return $SetupProcess + } +} \ No newline at end of file diff --git a/powershell/Invoke-StartAlteryx.ps1 b/powershell/Invoke-StartAlteryx.ps1 index 01717c1..dfe58f0 100644 --- a/powershell/Invoke-StartAlteryx.ps1 +++ b/powershell/Invoke-StartAlteryx.ps1 @@ -10,16 +10,16 @@ function Invoke-StartAlteryx { File name: Invoke-StartAlteryx.ps1 Author: Florian Carrier Creation date: 2021-07-08 - Last modified: 2022-04-19 + Last modified: 2024-09-11 #> [CmdletBinding ( SupportsShouldProcess = $true )] Param ( [Parameter ( - Position = 1, - Mandatory = $true, - HelpMessage = "Script properties" + Position = 1, + Mandatory = $true, + HelpMessage = "Script properties" )] [ValidateNotNullOrEmpty ()] [System.Collections.Specialized.OrderedDictionary] @@ -34,28 +34,36 @@ function Invoke-StartAlteryx { # Get global preference vrariables Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState # Log function call - Write-Log -Type "DEBUG" -Message $MyInvocation.ScriptName - # Variables - $ServiceName = "AlteryxService" + Write-Log -Type "DEBUG" -Message $MyInvocation.MyCommand.Name + # Process status + $StartProcess = New-ProcessObject -Name $MyInvocation.MyCommand.Name # Retrieve Alteryx Service utility path $AlteryxService = Get-AlteryxUtility -Utility "Service" -Path $Properties.InstallationPath + # Check installed version + $InstalledVersion = Get-AlteryxVersion + if (Compare-Version -Version $InstalledVersion -Operator "ne" -Reference $Properties.Version) { + Write-Log -Type "WARN" -Message "The configured version ($($Properties.Version)) does not match the version currently installed ($InstalledVersion)" + $Properties.Version = $InstalledVersion + } } Process { + $StartProcess = Update-ProcessObject -ProcessObject $StartProcess -Status "Running" Write-Log -Type "INFO" -Message "Starting Alteryx Service" - if ($PSCmdlet.ShouldProcess("Alteryx Service", "Start")) { - # Check service status - $WindowsService = Get-Service -Name $ServiceName - Write-Log -Type "DEBUG" -Message $WindowsService - if ($WindowsService.Status -eq "Running") { - Write-Log -Type "WARN" -Message "Alteryx Service ($ServiceName) is already running" - } else { + # Check service status + $WindowsService = Get-Service -Name $Properties.ServiceName + Write-Log -Type "DEBUG" -Message $WindowsService + if ($WindowsService.Status -eq "Running") { + Write-Log -Type "WARN" -Message "Alteryx Service ($($Properties.ServiceName)) is already running" + $StartProcess = Update-ProcessObject -ProcessObject $StartProcess -Status "Completed" -Success $true + } else { + if ($PSCmdlet.ShouldProcess("Alteryx Service", "Start")) { if ($Unattended -eq $false) { $Confirm = Confirm-Prompt -Prompt "Do you want to start the Alteryx Service?" } if ($Unattended -Or ($Confirm -eq $true)) { # Start service - $Process = Start-Process -FilePath $AlteryxService -ArgumentList "start" -Verb "RunAs" -PassThru -Wait - Write-Log -Type "DEBUG" -Message $Process + $ServiceProcess = Start-Process -FilePath $AlteryxService -ArgumentList "start" -Verb "RunAs" -PassThru -Wait + Write-Log -Type "DEBUG" -Message $ServiceProcess # Check process outcome if (Compare-Version -Version $Properties.Version -Operator "ge" -Reference "2021.4.1") { $ExpectedExitCode = 0 @@ -63,26 +71,37 @@ function Invoke-StartAlteryx { # Do not ask $ExpectedExitCode = 2 } - if ($Process.ExitCode -eq $ExpectedExitCode) { + if ($ServiceProcess.ExitCode -eq $ExpectedExitCode) { # Wait for service to start - while ((Get-Service -Name $ServiceName).Status -eq "StartPending") { + while ((Get-Service -Name $Properties.ServiceName).Status -eq "StartPending") { Write-Log -Type "INFO" -Message "Alteryx Service is starting..." Start-Sleep -Seconds 1 } # Check status - if ((Get-Service -Name $ServiceName).Status -eq "Running") { - Write-Log -Type "DEBUG" -Message (Get-Service -Name $ServiceName) + if ((Get-Service -Name $Properties.ServiceName).Status -eq "Running") { + Write-Log -Type "DEBUG" -Message (Get-Service -Name $Properties.ServiceName) Write-Log -Type "CHECK" -Message "Alteryx Service successfully started" + $StartProcess = Update-ProcessObject -ProcessObject $StartProcess -Status "Completed" -Success $true } else { - Write-Log -Type "ERROR" -Message "Attempt to start the Alteryx Service ($ServiceName) failed" -ExitCode 1 + Write-Log -Type "ERROR" -Message "Attempt to start the Alteryx Service ($($Properties.ServiceName)) failed" + $StartProcess = Update-ProcessObject -ProcessObject $StartProcess -Status "Failed" -ErrorCount 1 -ExitCode 1 } } else { - Write-Log -Type "ERROR" -Message "Alteryx Service could not be started" -ExitCode $Process.ExitCode + Write-Log -Type "DEBUG" -Message $ServiceProcess.ExitCode + Write-Log -Type "ERROR" -Message "Alteryx Service could not be started" + $StartProcess = Update-ProcessObject -ProcessObject $StartProcess -Status "Failed" -ErrorCount 1 -ExitCode $ServiceProcess.ExitCode } } else { - Write-Log -Type "WARN" -Message "Action was cancelled by the user" -ExitCode 0 + Write-Log -Type "WARN" -Message "Action was cancelled by the user" + $StartProcess = Update-ProcessObject -ProcessObject $StartProcess -Status "Cancelled" } + } else { + # Dummy success return for test run + $StartProcess = Update-ProcessObject -ProcessObject $StartProcess -Status "Completed" -Success $true } } } + End { + return $StartProcess + } } \ No newline at end of file diff --git a/powershell/Invoke-StopAlteryx.ps1 b/powershell/Invoke-StopAlteryx.ps1 index 6a34dcc..afed155 100644 --- a/powershell/Invoke-StopAlteryx.ps1 +++ b/powershell/Invoke-StopAlteryx.ps1 @@ -10,7 +10,7 @@ function Invoke-StopAlteryx { File name: Invoke-StopAlteryx.ps1 Author: Florian Carrier Creation date: 2021-07-08 - Last modified: 2022-04-19 + Last modified: 2024-09-11 #> [CmdletBinding ( SupportsShouldProcess = $true @@ -34,19 +34,21 @@ function Invoke-StopAlteryx { # Get global preference vrariables Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState # Log function call - Write-Log -Type "DEBUG" -Message $MyInvocation.ScriptName - # Variables - $ServiceName = "AlteryxService" + Write-Log -Type "DEBUG" -Message $MyInvocation.MyCommand.Name + # Process status + $StopProcess = New-ProcessObject -Name $MyInvocation.MyCommand.Name # Retrieve Alteryx Service utility path $AlteryxService = Get-AlteryxUtility -Utility "Service" -Path $Properties.InstallationPath } Process { + $StopProcess = Update-ProcessObject -ProcessObject $StopProcess -Status "Running" Write-Log -Type "INFO" -Message "Stopping Alteryx Service" # Check service status - $WindowsService = Get-Service -Name $ServiceName + $WindowsService = Get-Service -Name $Properties.ServiceName Write-Log -Type "DEBUG" -Message $WindowsService if ($WindowsService.Status -eq "Stopped") { - Write-Log -Type "WARN" -Message "Alteryx Service ($ServiceName) is already stopped" + Write-Log -Type "WARN" -Message "Alteryx Service ($($Properties.ServiceName)) is already stopped" + $StopProcess = Update-ProcessObject -ProcessObject $StopProcess -Status "Completed" -Success $true } else { if ($PSCmdlet.ShouldProcess("Alteryx Service", "Stop")) { if ($Unattended -eq $false) { @@ -54,29 +56,38 @@ function Invoke-StopAlteryx { } if ($Unattended -Or ($Confirm -eq $true)) { # Stop service - $Process = Start-Process -FilePath $AlteryxService -ArgumentList "stop" -Verb "RunAs" -PassThru -Wait - Write-Log -Type "DEBUG" -Message $Process + $ServiceProcess = Start-Process -FilePath $AlteryxService -ArgumentList "stop" -Verb "RunAs" -PassThru -Wait + Write-Log -Type "DEBUG" -Message $ServiceProcess # Check process outcome - if ($Process.ExitCode -eq 0) { + if ($ServiceProcess.ExitCode -eq 0) { # Wait for service to stop - while ((Get-Service -Name $ServiceName).Status -eq "StopPending") { + while ((Get-Service -Name $Properties.ServiceName).Status -eq "StopPending") { Write-Log -Type "INFO" -Message "Alteryx Service is stopping..." Start-Sleep -Seconds 1 } # Check status - if ((Get-Service -Name $ServiceName).Status -eq "Stopped") { - Write-Log -Type "DEBUG" -Message (Get-Service -Name $ServiceName) + if ((Get-Service -Name $Properties.ServiceName).Status -eq "Stopped") { + Write-Log -Type "DEBUG" -Message (Get-Service -Name $Properties.ServiceName) Write-Log -Type "CHECK" -Message "Alteryx Service successfully stopped" + $StopProcess = Update-ProcessObject -ProcessObject $StopProcess -Status "Completed" -Success $true } else { - Write-Log -Type "ERROR" -Message "Attempt to stop the Alteryx Service ($ServiceName) failed" -ExitCode 1 + Write-Log -Type "DEBUG" -Message $ServiceProcess + Write-Log -Type "ERROR" -Message "Attempt to stop the Alteryx Service ($Properties.ServiceName) failed" + $StopProcess = Update-ProcessObject -ProcessObject $StopProcess -Status "Failed" -ExitCode 1 } } else { - Write-Log -Type "ERROR" -Message "Alteryx Service could not be stopped" -ExitCode $Process.ExitCode + Write-Log -Type "DEBUG" -Message $ServiceProcess + Write-Log -Type "ERROR" -Message "Alteryx Service could not be stopped" + $StopProcess = Update-ProcessObject -ProcessObject $StopProcess -Status "Failed" -ExitCode $ServiceProcess.ExitCode } } else { - Write-Log -Type "WARN" -Message "Action was cancelled by the user" -ExitCode 0 + Write-Log -Type "WARN" -Message "Action was cancelled by the user" + $StopProcess = Update-ProcessObject -ProcessObject $StopProcess -Status "Cancelled" } } } } + End { + return $StopProcess + } } \ No newline at end of file diff --git a/powershell/Open-Alteryx.ps1 b/powershell/Open-Alteryx.ps1 new file mode 100644 index 0000000..62c5f28 --- /dev/null +++ b/powershell/Open-Alteryx.ps1 @@ -0,0 +1,84 @@ +function Open-Alteryx { + <# + .SYNOPSIS + Open Alteryx Server + + .DESCRIPTION + Open the default web-browser and navigate to Alteryx Gallery + + .NOTES + File name: Open-Alteryx.ps1 + Author: Florian Carrier + Creation date: 2024-09-16 + Last modified: 2024-09-16 + #> + [CmdletBinding ( + SupportsShouldProcess = $true + )] + Param ( + [Parameter ( + Position = 1, + Mandatory = $true, + HelpMessage = "Script properties" + )] + [ValidateNotNullOrEmpty ()] + [System.Collections.Specialized.OrderedDictionary] + $Properties, + [Parameter ( + HelpMessage = "Non-interactive mode" + )] + [Switch] + $Unattended + ) + Begin { + # Get global preference vrariables + Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState + # Log function call + Write-Log -Type "DEBUG" -Message $MyInvocation.MyCommand.Name + # Process status + $OpenProcess= New-ProcessObject -Name $MyInvocation.MyCommand.Name + # Parameters + $HostName = [System.Net.Dns]::GetHostName() + } + Process { + $OpenProcess = Update-ProcessObject -ProcessObject $OpenProcess -Status "Running" + Write-Log -Type "NOTICE" -Message "Opening Alteryx $($Properties.Product)" + if ($Properties.Product -eq "Server") { + Write-Log -Type "INFO" -Message "Opening Alteryx Gallery" + if ($PSCmdlet.ShouldProcess("Alteryx Gallery", "Open")) { + if ($Properties.EnableSSL -eq $true) { + $Protocol = "https" + } else { + $Protocol = "http" + } + $GalleryURL = "$($Protocol)://$($HostName)/gallery" + Start-Process -FilePath $GalleryURL + Write-Log -Type "CHECK" -Message "Alteryx Gallery opened successfully" + $OpenProcess = Update-ProcessObject -ProcessObject $OpenProcess -Status "Completed" -Success $true + } else { + # WhatIf + $OpenProcess = Update-ProcessObject -ProcessObject $OpenProcess -Status "Completed" -Success $true + } + } else { + if ($PSCmdlet.ShouldProcess("Alteryx $($Properties.Product)", "Open")) { + $DesignerPath = Get-AlteryxUtility -Utility $Properties.Product + $DesignerProcess = Start-Process -FilePath $DesignerPath -PassThru + $DesignerProcess.Refresh() + Write-Log -Type "DEBUG" -Message $DesignerProcess + if ($DesignerProcess.Responding -eq $true) { + Write-Log -Type "CHECK" -Message "Alteryx $($Properties.Product) opened successfully" + $OpenProcess = Update-ProcessObject -ProcessObject $OpenProcess -Status "Completed" -Success $true + } else { + Write-Log -Type "ERROR" -Message "Alteryx $($Properties.Product) could not be started" + $OpenProcess = Update-ProcessObject -ProcessObject $OpenProcess -Status "Failed" -ErrorCount 1 -ExitCode 1 + } + } else { + Write-Log -Type "CHECK" -Message "Alteryx $($Properties.Product) opened successfully" + $OpenProcess = Update-ProcessObject -ProcessObject $OpenProcess -Status "Completed" -Success $true + } + } + } + End { + return $OpenProcess + } +} \ No newline at end of file diff --git a/powershell/Repair-Alteryx.ps1 b/powershell/Repair-Alteryx.ps1 index bd75f20..643f338 100644 --- a/powershell/Repair-Alteryx.ps1 +++ b/powershell/Repair-Alteryx.ps1 @@ -47,7 +47,7 @@ function Repair-Alteryx { $Command = "--rebuild -mongoconnection:mongodb://user:@:/AlteryxGallery?connectTimeoutMS=25000 -luceneconnection:mongodb://user:@:/AlteryxGallery_Lucene?connectTimeoutMS=25000 -searchProvider:Lucene" } Process { - Write-Log -Type "INFO" -Message "Starting repair of Alteryx $($InstallationProperties.Product) $($Properties.Version)" + Write-Log -Type "INFO" -Message "Starting repair of Alteryx $($Properties.Product) $($Properties.Version)" $Path = Get-AlteryxUtility -Utility $Utility # Retrieve database password $Passwords = Get-AlteryxEMongoPassword diff --git a/powershell/Set-AlteryxConfiguration.ps1 b/powershell/Set-AlteryxConfiguration.ps1 new file mode 100644 index 0000000..d9f4397 --- /dev/null +++ b/powershell/Set-AlteryxConfiguration.ps1 @@ -0,0 +1,55 @@ +function Set-AlteryxConfiguration { + <# + .SYNOPSIS + Configure Alteryx + + .DESCRIPTION + Automatically configure Alteryx System Settings + + .NOTES + File name: Set-Configuration.ps1 + Author: Florian Carrier + Creation date: 2022-05-03 + Last modified: 2024-09-20 + #> + [CmdletBinding ( + SupportsShouldProcess = $true + )] + Param ( + [Parameter ( + Position = 1, + Mandatory = $true, + HelpMessage = "Script properties" + )] + [ValidateNotNullOrEmpty ()] + [System.Collections.Specialized.OrderedDictionary] + $Properties, + [Parameter ( + HelpMessage = "Non-interactive mode" + )] + [Switch] + $Unattended + ) + Begin { + # Get global preference vrariables + Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState + # Log function call + Write-Log -Type "DEBUG" -Message $MyInvocation.MyCommand.Name + # Process status + $ConfigureProcess = New-ProcessObject -Name $MyInvocation.MyCommand.Name + } + Process { + $ConfigureProcess = Update-ProcessObject -ProcessObject $ConfigureProcess -Status "Running" + if ($PSCmdlet.ShouldProcess("Alteryx System Settings", "Configure")) { + # TODO + Write-Log -Type "WARN" -Message "Automated configuration of Alteryx is not yet supported" + Write-Log -Type "WARN" -Message "Please configure the application through Alteryx System Settings" + $ConfigureProcess = Update-ProcessObject -ProcessObject $ConfigureProcess -Status "Cancelled" + } else { + $ConfigureProcess = Update-ProcessObject -ProcessObject $ConfigureProcess -Status "Completed" -Success $true + } + } + End { + return $ConfigureProcess + } +} \ No newline at end of file diff --git a/powershell/Show-Configuration.ps1 b/powershell/Show-Configuration.ps1 index ae48151..708a5e0 100644 --- a/powershell/Show-Configuration.ps1 +++ b/powershell/Show-Configuration.ps1 @@ -16,7 +16,7 @@ function Show-Configuration { File name: Show-Configuration.ps1 Author: Florian Carrier Creation date: 2021-07-08 - Last modified: 2022-04-19 + Last modified: 2024-09-11 #> [CmdletBinding ()] Param ( @@ -41,16 +41,24 @@ function Show-Configuration { # Get global preference variables Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState # Log function call - Write-Log -Type "DEBUG" -Message $MyInvocation.ScriptName + Write-Log -Type "DEBUG" -Message $MyInvocation.MyCommand.Name + # Process status + $ShowProcess = New-ProcessObject -Name $MyInvocation.MyCommand.Name # Display colour $Colour = "Cyan" } Process { - # Display default x custom script configuration - Write-Log -Type "INFO" -Object "Script configuration" + $ShowProcess = Update-ProcessObject -ProcessObject $ShowProcess -Status "Running" + Write-Log -Type "NOTICE" -Message "Displaying script configuration" + # Display default x custom script parameters + Write-Log -Type "INFO" -Object "Script parameters" Write-Host -Object ($Properties | Out-String).Trim() -ForegroundColor $Colour - # Display installation configuration - Write-Log -Type "INFO" -Object "Installation configuration" + # Display installation parameters + Write-Log -Type "INFO" -Object "Installation parameters" Write-Host -Object ($InstallationProperties | Out-String).Trim() -ForegroundColor $Colour + $ShowProcess = Update-ProcessObject -ProcessObject $ShowProcess -Status "Completed" -Success $true + } + End { + return $ShowProcess } } \ No newline at end of file diff --git a/powershell/Show-Help.ps1 b/powershell/Show-Help.ps1 new file mode 100644 index 0000000..9b5f14b --- /dev/null +++ b/powershell/Show-Help.ps1 @@ -0,0 +1,46 @@ +function Show-Help { + <# + .SYNOPSIS + Show help documentation + + .DESCRIPTION + Displays the help documentation and provide usefull links + + .NOTES + File name: Show-Help.ps1 + Author: Florian Carrier + Creation date: 2024-09-23 + Last modified: 2024-09-23 + #> + Begin { + # Get global preference vrariables + Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState + # Log function call + Write-Log -Type "DEBUG" -Message $MyInvocation.MyCommand.Name + # Process status + $HelpProcess = New-ProcessObject -Name $MyInvocation.MyCommand.Name + # Variables + $Path = Resolve-Path -Path "$PSScriptRoot\..\" + $Script = "Deploy-Alteryx.ps1" + $ReadMe = "README.md" + } + Process { + $HelpProcess = Update-ProcessObject -ProcessObject $HelpProcess -Status "Running" + Write-Log -Type "NOTICE" -Message @" +Help documentation + _ _ _ _ + __ _| | |_ ___ _ __ _ ___ __ __| | ___ _ __ | | ___ _ _ + / _`` | | __/ _ \ '__| | | \ \/ /____ / _`` |/ _ \ '_ \| |/ _ \| | | | +| (_| | | || __/ | | |_| |> <|____| (_| | __/ |_) | | (_) | |_| | + \__,_|_|\__\___|_| \__, /_/\_\ \__,_|\___| .__/|_|\___/ \__, | + |___/ |_| |___/ +"@ + $Documentation = Get-Help -Name "$Path\$Script" -Full + Write-Log -Type "INFO" -Message $Documentation + Write-Log -Type "INFO" -Message "Additional information is available in the README file ($PSScriptRoot\$ReadMe)" + $HelpProcess = Update-ProcessObject -ProcessObject $HelpProcess -Status "Completed" -Success $true + } + End { + return $HelpProcess + } +} \ No newline at end of file diff --git a/powershell/Uninstall-Alteryx.ps1 b/powershell/Uninstall-Alteryx.ps1 index 215cb9f..8d4879b 100644 --- a/powershell/Uninstall-Alteryx.ps1 +++ b/powershell/Uninstall-Alteryx.ps1 @@ -16,13 +16,13 @@ function Uninstall-Alteryx { File name: Uninstall-Alteryx.ps1 Author: Florian Carrier Creation date: 2021-07-08 - Last modified: 2022-04-19 + Last modified: 2024-09-24 .LINK https://www.powershellgallery.com/packages/PSAYX .LINK - https://help.alteryx.com/current/product-activation-and-licensing/use-command-line-options + https://help.alteryx.com/current/en/license-and-activate/administer/use-command-line-options.html .LINK https://community.alteryx.com/t5/Alteryx-Designer-Knowledge-Base/Complete-Uninstall-of-Alteryx-Designer/ta-p/402897 @@ -58,51 +58,112 @@ function Uninstall-Alteryx { # Get global preference variables Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState # Log function call - Write-Log -Type "DEBUG" -Message $MyInvocation.ScriptName + Write-Log -Type "DEBUG" -Message $MyInvocation.MyCommand.Name + # Process status + $Uninstallprocess = New-ProcessObject -Name $MyInvocation.MyCommand.Name # Variables $ISOTimeStamp = Get-Date -Format "yyyyMMdd_HHmmss" $Tags = [Ordered]@{"Version" = $Properties.Version} # Filenames - if ($InstallationProperties.Product -eq "Designer") { + if ($Properties.Product -eq "Designer") { $ServerInstaller = "AlteryxInstallx64_.exe" } else { $ServerInstaller = "AlteryxServerInstallx64_.exe" - } + } } Process { - Write-Log -Type "INFO" -Message "Uninstallation of Alteryx Server $($Properties.Version)" + $Uninstallprocess = Update-ProcessObject -ProcessObject $Uninstallprocess -Status "Running" + Write-Log -Type "NOTICE" -Message "Uninstallation of Alteryx Server $($Properties.Version)" + # ------------------------------------------------------------------------------ + # * Checks + # ------------------------------------------------------------------------------ + if ($Unattended -eq $false) { + # Ask for confirmation to uninstall + $ConfirmUninstall = Confirm-Prompt -Prompt "Are you sure that you want to uninstall $($Properties.Product)?" + if ($ConfirmUninstall -eq $false) { + Write-Log -Type "WARN" -Message "Cancelling uninstallation" + $Uninstallprocess = Update-ProcessObject -ProcessObject $Uninstallprocess -Status "Cancelled" + return $Uninstallprocess + } else { + # TODO check if Alteryx is installed + # Suggest backup + $Backup = Confirm-Prompt -Prompt "Do you want to take a back-up of the database?" + if ($Backup) { + $BackupProcess = Invoke-BackupAlteryx -Properties $Properties -Unattended:$Unattended + if ($BackupProcess.Success -eq $false) { + if (Confirm-Prompt -Prompt "Do you still want to proceed with the uninstallation?") { + $Uninstallprocess = Update-ProcessObject -ProcessObject $Uninstallprocess -ErrorCount 1 + } else { + $Uninstallprocess = Update-ProcessObject -ProcessObject $Uninstallprocess -Status "Cancelled" -ErrorCount 1 + return $Uninstallprocess + } + } + } else { + Write-Log -Type "WARN" -Message "Skipping database back-up" + } + } + } + # ------------------------------------------------------------------------------ + # * Deactivate license keys + # ------------------------------------------------------------------------------ + $DeactivateProcess = Invoke-DeactivateAlteryx -Properties $Properties -All -Unattended:$Unattended + if ($DeactivateProcess.Success -eq $false) { + $Uninstallprocess = Update-ProcessObject -ProcessObject $Uninstallprocess -ErrorCount 1 + } # ------------------------------------------------------------------------------ - # Alteryx Server + # * Uninstall Alteryx Server # ------------------------------------------------------------------------------ - # TODO check if Alteryx is installed - # Deactivate license keys - Invoke-DeactivateAlteryx -Properties $Properties -All -Unattended:$Unattended # Update file version number $ServerFileName = Set-Tags -String $ServerInstaller -Tags (Resolve-Tags -Tags $Tags -Prefix "<" -Suffix ">") $ServerPath = Join-Path -Path $Properties.SrcDirectory -ChildPath $ServerFileName - Write-Log -Type "INFO" -Message "Uninstalling Alteryx $($InstallationProperties.Product)" + Write-Log -Type "INFO" -Message "Uninstalling Alteryx $($Properties.Product)" if ($PSCmdlet.ShouldProcess($ServerPath, "Uninstall")) { if (Test-Path -Path $ServerPath) { if ($Properties.InstallAwareLog -eq $true) { $InstallAwareLog = Join-Path -Path $Properties.LogDirectory -ChildPath "${ISOTimeStamp}_${ServerFileName}.log" - $ServerUninstall = Uninstall-AlteryxServer -Path $ServerPath -Log $InstallAwareLog -Unattended:$Unattended + $ServerUninstall = Uninstall-AlteryxServer -Path $ServerPath -Version $Properties.Version -Log $InstallAwareLog -Unattended:$Unattended } else { - $ServerUninstall = Uninstall-AlteryxServer -Path $ServerPath -Unattended:$Unattended + $ServerUninstall = Uninstall-AlteryxServer -Path $ServerPath -Version $Properties.Version -Unattended:$Unattended } Write-Log -Type "DEBUG" -Message $ServerUninstall if ($ServerUninstall.ExitCode -eq 0) { Write-Log -Type "CHECK" -Message "Alteryx Server uninstalled successfully" } else { Write-Log -Type "ERROR" -Message "An error occured during the uninstallation" -ExitCode $ServerUninstall.ExitCode + $Uninstallprocess = Update-ProcessObject -ProcessObject $Uninstallprocess -Status "Failed" -ErrorCount 1 -ExitCode 1 + return $Uninstallprocess + } } else { Write-Log -Type "ERROR" -Message "Path not found $ServerPath" - Write-Log -Type "ERROR" -Message "Alteryx $($InstallationProperties.Product) executable file could not be located" -ExitCode 1 + Write-Log -Type "ERROR" -Message "Alteryx $($Properties.Product) executable file could not be located" + $Uninstallprocess = Update-ProcessObject -ProcessObject $Uninstallprocess -Status "Failed" -ErrorCount 1 -ExitCode 1 + return $Uninstallprocess } } # TODO remove leftover files # TODO remove registry keys + # ------------------------------------------------------------------------------ + # * Uninstall add-ons + # ------------------------------------------------------------------------------ # TODO enable uninstall of standalone components - Write-Log -Type "CHECK" -Message "Uninstallation of Alteryx $($InstallationProperties.Product) $Version successfull" + # ------------------------------------------------------------------------------ + # * Check + # ------------------------------------------------------------------------------ + if ($Uninstallprocess.ErrorCount -eq 0) { + Write-Log -Type "CHECK" -Message "Uninstallation of Alteryx $($Properties.Product) $Version successfull" + $Uninstallprocess = Update-ProcessObject -ProcessObject $Uninstallprocess -Status "Completed" -Success $true + } else { + if ($Uninstallprocess.ErrorCount -eq 1) { + $ErrorCount = "one error" + } else { + $ErrorCount = "$($Uninstallprocess.ErrorCount) errors" + } + Write-Log -Type "WARN" -Message "Alteryx $($Properties.Product) $($Properties.Version) was uninstalled with $ErrorCount" + $Uninstallprocess = Update-ProcessObject -ProcessObject $Uninstallprocess -Status "Completed" + } + } + End { + return $Uninstallprocess } } \ No newline at end of file diff --git a/powershell/Update-Alteryx.ps1 b/powershell/Update-Alteryx.ps1 index 6b6f618..33413c0 100644 --- a/powershell/Update-Alteryx.ps1 +++ b/powershell/Update-Alteryx.ps1 @@ -4,13 +4,13 @@ function Update-Alteryx { Update Alteryx .DESCRIPTION - Upgrade Alteryx Server + Upgrade Alteryx Server and rollback if process fails .NOTES File name: Update-Alteryx.ps1 Author: Florian Carrier Creation date: 2021-09-02 - Last modified: 2022-04-19 + Last modified: 2024-09-23 #> [CmdletBinding ( SupportsShouldProcess = $true @@ -42,7 +42,9 @@ function Update-Alteryx { # Get global preference vrariables Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState # Log function call - Write-Log -Type "DEBUG" -Message $MyInvocation.ScriptName + Write-Log -Type "DEBUG" -Message $MyInvocation.MyCommand.Name + # Process status + $UpgradeProcess = New-ProcessObject -Name $MyInvocation.MyCommand.Name # Clear error pipeline $Error.Clear() # Retrieve current version @@ -57,9 +59,11 @@ function Update-Alteryx { $Properties.ActivateOnInstall = $Properties.ActivateOnUpgrade } Process { + $UpgradeProcess = Update-ProcessObject -ProcessObject $UpgradeProcess -Status "Running" Write-Log -Type "CHECK" -Object "Starting Alteryx Server upgrade from $BackupVersion to $($Properties.Version)" # Check installation path - $InstallationPath = Get-AlteryxInstallDirectory + $InstallDirectory = Get-AlteryxInstallDirectory + $InstallationPath = Resolve-Path -Path "$InstallDirectory\.." if ($Properties.InstallationPath -ne $InstallationPath) { # If new installation directory is specified Write-Log -Type "WARN" -Message "New installation directory specified" @@ -71,30 +75,71 @@ function Update-Alteryx { if ($Confirm -Or $Unattended) { $AlteryxService = Get-AlteryxUtility -Utility "Service" -Path $InstallationPath } else { - Write-Log -Type "WARN" -Message "Upgrade cancelled by user" -ExitCode 0 + Write-Log -Type "WARN" -Message "Upgrade cancelled by user" + $UpgradeProcess = Update-ProcessObject -ProcessObject $UpgradeProcess -Status "Cancelled" + return $UpgradeProcess } } else { # Retrieve Alteryx Service utility path $AlteryxService = Get-AlteryxUtility -Utility "Service" -Path $Properties.InstallationPath } + # ------------------------------------------------------------------------------ + # * Back-up + # ------------------------------------------------------------------------------ # Create back-up $BackUpProperties = Copy-OrderedHashtable -Hashtable $Properties $BackUpProperties.Version = $BackupVersion $BackupProperties.InstallationPath = $InstallationPath - Invoke-BackupAlteryx -Properties $BackUpProperties -Unattended:$Unattended - # Upgrade - Install-Alteryx -Properties $Properties -InstallationProperties $InstallationProperties -Unattended:$Unattended - # Check for errors - if ($Error.Count -gt 0) { - # Rollback - Write-Log -Type "ERROR" -Object "Upgrade process failed with $($Error.Count) errors" - Write-Log -Type "WARN" -Object "Restoring previous version ($BackupVersion)" - # Reinstall Alteryx - Install-Alteryx -Properties $BackUpProperties -Unattended:$Unattended - # Restore backup - Invoke-RestoreAlteryx -Properties $BackUpProperties -Unattended:$Unattended - } else { + $BackupProcess = Invoke-BackupAlteryx -Properties $BackUpProperties -Unattended:$Unattended + if ($BackupProcess.Success -eq $false) { + if (Confirm-Prompt -Prompt "Do you still want to proceed with the upgrade?") { + $UpgradeProcess = Update-ProcessObject -ProcessObject $UpgradeProcess -ErrorCount $BackupProcess.ErrorCount + } else { + $UpgradeProcess = Update-ProcessObject -ProcessObject $UpgradeProcess -Status "Cancelled" -ErrorCount $BackupProcess.ErrorCount + return $UpgradeProcess + } + } + # ------------------------------------------------------------------------------ + # * Upgrade + # ------------------------------------------------------------------------------ + $InstallProcess = Install-Alteryx -Properties $Properties -InstallationProperties $InstallationProperties -Unattended:$Unattended + if ($InstallProcess.Success -eq $true) { Write-Log -Type "CHECK" -Object "Alteryx Server upgrade completed successfully" + } else { + Write-Log -Type "ERROR" -Object "Upgrade process failed" + $UpgradeProcess = Update-ProcessObject -ProcessObject $UpgradeProcess -Status "Failed" -ErrorCount $InstallProcess.ErrorCount -ExitCode $InstallProcess.ExitCode + # ------------------------------------------------------------------------------ + # * Rollback + # ------------------------------------------------------------------------------ + $RollbackProcess = Invoke-RollbackAlteryx -Properties $Properties -InstallationProperties $InstallationProperties -Unattended:$Unattended + if ($RollbackProcess.Success -eq $false) { + Write-Log -Type "ERROR" -Message "Rollback process failed" + $UpgradeProcess = Update-ProcessObject -ProcessObject $UpgradeProcess -Status "Failed" -ErrorCount $RollbackProcess.ErrorCount -ExitCode $RollbackProcess.ExitCode + return $UpgradeProcess + } } + # ------------------------------------------------------------------------------ + # * Checks + # ------------------------------------------------------------------------------ + if ($UpgradeProcess.ErrorCount -eq 0) { + Write-Log -Type "CHECK" -Message "Alteryx $($Properties.Product) $($Properties.Version) upgraded successfully" + $UpgradeProcess = Update-ProcessObject -ProcessObject $UpgradeProcess -Status "Completed" -Success $true + } else { + if ($UpgradeProcess.ErrorCount -eq 1) { + $ErrorCount = "one error" + } else { + $ErrorCount = "$($UpgradeProcess.ErrorCount) errors" + } + if ($null -eq $RollbackProcess) { + Write-Log -Type "WARN" -Message "Alteryx $($Properties.Product) $($Properties.Version) was upgraded with $ErrorCount" + $UpgradeProcess = Update-ProcessObject -ProcessObject $UpgradeProcess -Status "Completed" + } else { + Write-Log -Type "WARN" -Message "Alteryx $($Properties.Product) $($Properties.Version) upgraded process failed with $ErrorCount" + $UpgradeProcess = Update-ProcessObject -ProcessObject $UpgradeProcess -Status "Failed" -ExitCode 1 + } + } + } + End { + return $UpgradeProcess } } \ No newline at end of file