<#
.SYNOPSIS
    Deploys a Wizdom environment to SharePoint Online and Azure based on predefined configuration in JSON file. Currently based on "Wizdom Office 365 Installation Guide v4.2.0"

.DESCRIPTION
    Requirements:
    - Powershell v5
    - AzureRM, Azure, MSOnline and SharePointPnPPowerShellOnline module
    - SharePoint Management Shell, Sign-in assistant and Web Deployment Tools

    Both modules and tools will be validated and potentially updated, if the script are executed with the '-checkPrerequisites' switch.

    The script will perform the following actions:
    - Check user credentials and access rights
    - Verify the configuration settings and make sure everything will run as smooth as possible
    - Check if the app catalog exists on the tenant
    - Update / install Puppeteer and dependencies like Chromium in the installation directory
    - Create the Wizdom Search User is necessary
    - Create the Wizdom Intranet Site if it doesn't already exists
    - Add the Search user to the Visitors Group
    - Create the Wizdom Data Site if it doesn't already exists
    - Activate Publishing features on the Intranet Site
    - Deactivate Mobile Browser View feature on the Intranet Site
    - Create an Azure AD app
    - Create an Azure Group with Services for the Wizdom installation
    - Perform necessary configuration of the Azure App Service
    - Deploy Wizdom to the App Service
    - Create and Upload a SharePoint App
    - Tust the App on tenant level
    - Install Wizdom on the Intranet Site
    - Set tenant level search configuration
    - Pair the Wizdom installation with the Wizdom License Service
    - Perform necessary configuration of Wizdom

    With the -WhatIf switch, only the testing and verification of the above actions will be performed. No CREATE actions are actually performed.

.PARAMETER configFile
    Path and filename pointing to the json configuration file used as template for the installation (Mandatory)

.PARAMETER SharePointTenantAdmin
    The username of a user with Tenant Admin rights for Office 365 (including admin rights to SharePoint) for the Tenant referenced in the JSON config file (Mandatory)

.PARAMETER azureSubscriptionAdmin
    The username of a user with Owner / Admin rights to the Azure Subscription referenced in the JSON config file (Mandatory). It's recommended to use an admin user from the same tenant to avoid issues with missing access to AAD.

.PARAMETER environment
    The Wizdom environments to support. 
    - Classic: Sets up a Classic SharePoint Collection and prepares it with feature activation, Wizdom app etc
    - Modern: Deploys modern packages to the app catalog and sets the tenant to use this Wizdom instance as the Modern Wizdom Instance
    - Both.

.PARAMETER skipCheckPrerequisites
    (Optional) (Switch) Decides whether the script should check for the component prerequisites and prompt for updates if newer versions exists. They may not require an update for the script to work. Currently tested on AzureRM 6.8.1, Azure 5.3.0, MSOnline 1.1.183.17 and SharePointPnPPowerShellOnline 3.1.1809.0

.PARAMETER installMode
    (Optional) Defines what components to install:
    - Basic (default): Installs Wizdom on Azure and on SharePoint without additional addons
    - WithAppInsights: As Basic. Includes installation of Azure Application Insights and configuration of the Analytics Module in Wizdom
    - WithMobileSolution: As Basic. Includes installation of an extra Web Application Service in Azure and an extra SharePoint Site Collection for the Wizdom Mobile Application Custom Module
    - WithAppInsightsAndMobileSolution: As WithAppInsights and WithMobileSolution combined
    - NoneTestConfig: Only test the configuration file, no installation done. Used for initial installation as well as in combination with removeMode to remove a Wizdom installation entirely.
    
.PARAMETER removeMode
    (Optional) Removes existing resources before running the rest of the installation script:
    - AzureResourceGroup
    - AzureResourceGroup_SharePointSites
    - AzureResourceGroup_SharePointSites_Office365Users
    - AzureResourceGroup_Office365Users
    - AzureServices
    - AzureServices_SharePointSites
    - AzureServices_SharePointSites_Office365Users
    - AzureServices_Office365Users
    Parameter Explanation:
    - AzureResourceGroup: Removes the entire Wizdom Azure Resource Group
    - AzureServices: Removes the individual Azure Services used, but leaves the Resource Group and additional Services untouched
    - Office365Users: Removes used service accounts (Wizdom Search User and Wizdom Applications Insights User)
    - SharePointSites: Removes the Wizdom intranet site collection and datastore site collection

.PARAMETER UseWebLoginSharePoint
    (Optional) (Switch) Information that the script will be running in Multifactor Authentication mode for the SharePoint Tenant Admin User.

.PARAMETER UseWebLoginAzure
    (Optional) (Switch) Information that the script will be running in Multifactor Authentication mode for the Azure Subscription Admin User. Alternative to azureSubscriptionAdmin and azureSubscriptionAdminPassword parameters.

.PARAMETER SharePointTenantAdminPassword
    (Optional) The password of the SharePoint Tenant Admin user. For unattended execution. If not supplied the script will query for it as a secure string

.PARAMETER azureSubscriptionAdminPassword
    (Optional) The password of the Azure Subscription Admin user. For unattended execution. If not supplied the script will query for it as a secure string

.PARAMETER wizdomLicensePortalUser
    (Optional) The username of a user with access rights to Wizdoms internal license server. Used in conjunction with the LicenseID setting in the config file to generate license pins for unattended deployments. If not supplied the script will query for a PIN.

.PARAMETER wizdomLicensePortalUserPassword
    (Optional) The password of the Wizdom License Portal User. For unattended execution. If not supplied the script will query for it as a secure string

.PARAMETER ConfirmRemove
    (Optional) Swith to set whether potential deletes should prompt for confirmation then using removeMode

.EXAMPLE
    .\SharePointOnlineInstallation.ps1 -configfile .\SomeConfigFile.json -SharePointTenantAdmin <user>@<tenantname>.onmicrosoft.com -azureSubscriptionAdmin <user>@<tenantname>.onmicrosoft.com -installMode NoneTestConfig -environment Both
    
    Checks if software prerequisites are in place and if the config file has been configured correctly

.EXAMPLE
    .\SharePointOnlineInstallation.ps1 -configfile .\SomeConfigFile.json -SharePointTenantAdmin <user>@<tenantname>.onmicrosoft.com -azureSubscriptionAdmin <user>@<tenantname>.onmicrosoft.com -installMode WithAppInsights -environment Both
    
    Checks if software prerequisites are in place and installs Wizdom on Classic and Wizdom on Modern as well as the Wizdom Analytics Module

.EXAMPLE
    .\SharePointOnlineInstallation.ps1 -configfile .\SomeConfigFile.json -UseWebLoginSharePoint -UseWebLoginAzure -environment Both

    Checks if software prerequisites are in place and installs Wizdom on Classic and Wizdom on Modern using Multi-Factor Authentication on both Office365 and Azure.

.EXAMPLE
    .\SharePointOnlineInstallation.ps1 -configfile .\SomeConfigFile.json -SharePointTenantAdmin <user>@<tenantname>.onmicrosoft.com -SharePointTenantAdminPassword <password> -azureSubscriptionAdmin <user>@<tenantname>.onmicrosoft.com -azureSubscriptionAdminPassword <password> -skipCheckPrerequisites -installMode WithAppInsightsAndMobileSolution -environment Classic
    
    Installs Wizdom on Classic as well as the Wizdom Analytics Module and the Wizdom Mobile Solution. As passwords are supplied in the commandline, the installation can be executed unattended.

.EXAMPLE
    .\SharePointOnlineInstallation.ps1 -configfile .\SomeConfigFile.json -SharePointTenantAdmin <user>@<tenantname>.onmicrosoft.com -azureSubscriptionAdmin <user>@<tenantname>.onmicrosoft.com -skipCheckPrerequisites -installMode Basic -environment Modern
    
    Installs Wizdom and prepares for a Modern Implementation (no modern sitecollection created). It will qurey for passwords, but skip the prerequisites check, saving some time.

.EXAMPLE
    .\SharePointOnlineInstallation.ps1 -configfile .\SomeConfigFile.json -SharePointTenantAdmin <user>@<tenantname>.onmicrosoft.com -azureSubscriptionAdmin <user>@<tenantname>.onmicrosoft.com -skipCheckPrerequisites -installMode NoneTestConfig -removeMode AzureResourceGroup_SharePointSites -environment Classic
    
    Removes Wizdom Classic without installing.

.EXAMPLE
    (Wizdom internal)
    .\SharePointOnlineInstallation.ps1 -configfile .\SomeConfigFile.json -SharePointTenantAdmin <user>@<tenantname>.onmicrosoft.com -SharePointTenantAdminPassword <password> -azureSubscriptionAdmin <user>@<tenantname>.onmicrosoft.com -azureSubscriptionAdminPassword <password> -wizdomLicensePortalUser <user>@wizdom-intranet.com -wizdomLicensePortalUserPassword <password> -skipCheckPrerequisites -installMode WithAppInsightsAndMobileSolution -environment Classic
    
    Installs Wizdom on Classic as well as the Wizdom Analytics Module and the Wizdom Mobile Solution. As passwords are supplied in the commandline, the installation can be executed unattended. A new license key is automatically generated as part of the installation.


.NOTES
    AUTHOR: Christoffer Soltau
    LASTEDIT: 17-11-2019 
    v1.1
        PublishingSiteTemplate -	Valg mellem STS#0 og BLANKINTERNET#0 / BLANKINTERNETCONTAINER#0 som base template
        Support install on root site - needs more testing RL - did publishing activate ok? Tested only with recreated SC
        MFA for Azure Account
        Check o365Admin rights for app catalog
        Updated with documentation update 3.0.1: Updated email text, Updated output regarding post-config of Image renditions
        Updated with documentation update 3.0.2: Wizdom Global Termset added (and term store admin rights checked)
        Stabilization
    v2.0
        Get-RandomPassword updated to create valid passwords
        microsoft.insights automatically registered as providernamespace
        microsoft.insights separated with own location
        Support for Azure MSA accounts added
        Support for Multifactor Authentication added
        Support for password characters: SQL injection characters and invalid XML strings
        Bugfixes on Mobile Installation
        Improved password input handling
        Validation of search/application insights user passwords
        Improved internal user/service handling
        Improved handling of deleted site collections
        Parametersets introduced
        Support for Modern deployment
        Improved removal control
        - Support existing resource group - delete services
        - Option to delete existing 0365 users
        Support for WhatIf added
        Output Document for Operations added
        Stabilization
    v2.01
        Changed Modern app deployment endpoint from PUT to POST
        Changed Wizdom Data Site deletion to be silent
        Changed user rights assignments to Wizdom Data Site (Search account not added as it should)
    v2.02
        "-" allowed in site collection Urls
        Correction to mobile installation "NoneTestConfig" where check for webapp was performed if removeMode -eq ""
        Added check for search user license
    v2.03
        Updated documentation
        Added array formatting to get-wmiobjects
    v2.04
        Parameter namechanges: MFA parameters changed to UseWebLogin to conform with PnP cmdlets
    v2.05
        Added support for executing script from outside the working directory
        Test for invalid JSON
        Token replacement of Azure service naming rules
    v2.06
        Repeat License query on failed attempts
        Accept URL as intranet and data sitecollection name
        Azure Subscription Contributor Rights Sufficient
        Added test for Azure Storage Account name uniqueness
    v2.07
        Better output for manual actions when the Wizdom License PIN is not entered as part of the installation
        Check for Powershell v5 installed
        SkipPublisherCheck introduced as plan B when installing new modules to circumvent certificate errors in PnP framwork v 3.5
        Better handling of missing PIN code and PIN errors
        Set Read.Write Group delegate permissions for the Graph User for Teams Creation
        Check race condition on site collection creation vs Associated User Groups
        Checking user accountnames according to https://support.microsoft.com/en-us/help/2439357/error-message-when-you-try-to-create-a-user-name-that-contains-a-speci
    v2.08 (6.35 release)
		Updated access token generation for Wizdom endpoints
        Correction to test for Azure Storage Account uniqueness
        Added option for using existing AD app (untested)
        Added check to avoid use of SharePoint Online Management Shell
        Added check for Mobile installation Site Collcection in Recycle Bin - and subsequent deletion
		Improved permissions check for Azure Admins with Live accounts
        Added example of Modern installation
    v2.09 (6.36 release)
        Further improvements on Check race condition on site collection creation vs Associated User Groups
        Check for existence of configurable installation files
        Decorating CSOM calls with ISP id to avoid o365 throttling
        Added retry on CSOM calls for 429 and 503 errors (Throttling and Server unavailable: https://docs.microsoft.com/en-us/sharepoint/dev/general-development/how-to-avoid-getting-throttled-or-blocked-in-sharepoint-online)
        Improved control of wait on load and timeouts in the app trust puppeteer script
    v2.09a (6.36 release hotfix)    
        Fixed navigation promise / wait in puppeteer script for app trust
    v2.10 (6.37 release)
        Added try/catch around mail sending in the mobile installation
        Changed Wizdom config center link for manual Wizdom post-install actions
        Updated Wizdom License PIN logic to actually accept manually entered PINs
        Updated App trust to also work for pure 'Modern' installations
        Updated Modern manual deployment guide
        Fixed miss on checking App Catalog existence
        Updated New-WizdomADApp function to include tenantSiteUrl in the ReplyURLS, added trailing "/" to tenentSiteUrl in app settings and updated accessToken generation
        Setting Application Insights default Quota to 1 Gb per default
        Added additional properties to the search configuration settings
        Updated link to localeid documentation @ Microsoft
        Introduced wait when creating office365 users to ensure finished creation.
        Added support for modern admin site in puppeteer app trust script
        Updated to test for PowerShell version 5.1
        Added UseBasicParsing parameter to Invoke-WebRequest to avoid issues with missing IE assemblies
        Enabled TLS for SQL Connection
    v2.11 (6.38 release)
        Improved User deletion (deleting from Recycle Bin)
        Password ambersand issue when reading password from JSON file
    v2.12 (6.39 release)
		Silent delete of Wizdom data site from recycle bin
		Improved stability when creating new o365 users and setting security
        Added support for the AzureADPreview module (in general)
    v2.13 (6.40 release)
        Issues with support for the AzureADPreview module fixed
        RemoveMode AzureServices issue fixed - now it deletes all services
        Included deployment scaling based on user count
        Updated naming conventions to allow more complex companynames (case insensitive, 3-24 chars, numbers, hypens)
        Updated default Redis config to require TLS 1.2 instead of default TLS 1.0
        Add Teams Service Administrator Role to Search Account
        Default language setting (to reduce loaded translations file)
        Check if app trust is successful, prompting for manual action
        Adding default links to App Catalogue: Wizdom Config Center and Help & Training Center
        Added support for use of Azure KeyVault
        Support for existing AD app issues finished 
        Separated Wizdom Search and Graph User into two different O365 accounts
        Improved handling of site rights assignment on slow connections
        Mobile install updated to match Installation Guide v. 1.2
        New default location for FAQ set to SQL
    v2.14
        Checkprerequisites now support AzureADPreview module
        Changed publishing/mobile feature activation deactivation from CSOM to PNPOnline cmdlets
        Changed Get-TenantSettings to use Get-PnpTenantAppCatalogUrl
        Changed WizdomGlobalTermsets to use PnPTerm* cmdlets
        Changed CheckSiteExists to more simple call to Get-SPOSite (doesn't take missing permissions into account, but removes CSOM dependency)
        Dependency on app password removed to increase support for MFA-enabled environments
        Corrected bug in graph user teams admin rights assignment for existing users
        Changed dependency of *-MsolUser to the AzureAD Module similar cmdlets
        Removed Msol workaround to check if users where actually created in AzureAD.
        Updated Get-SPOSite to suppress error messages
        Fixed timeout issue in puppeteer script for app trust
        Fixed navigation issue in puppeteer script for app trust (Microsoft changed the markup).
        Puppeteer script updated to support MFA enabled installation (new key added in Wizdom section of the JSON config file: appTrustPuppeteerScriptMFA)
        Puppeteer script stabilized with internal retries when SharePoint doesn't register the new client ID immediately
        Updated base install script to support MFA enabled installation
        Updated Mobile install script to support MFA enabled installation
        Fixed issue with storing invalid passwords containing '&'
    v2.15
        Fixed error when logging into SharePoint with password, not using parameter
        Added support for multiple onmicrosoft.com domains.
        Script now automatically detects and deletes old SharePoint Apps when installing, instead of requiring the removeMode parameter (consequence of the new app based install method)
        Improved exit message for NoneTestConfig install mode
        Azure Service removal message for mobile installation corrected.
        Get-SPOSite calls extended with -Limit ALL to ensure correct query returns
        Created SP app now deleted when NoneTestConfig Exits script
        Timeout in Puppeteer script changed from 2 to 5 seconds, waiting for login box.
        Path to Mobile Install Files shortened below 260 chars.
        Added ConfirmRemove Parameter
        Changed error in Graph User registration in Azure Service App Settings (defaulted to searchuser, even with separate accounts given)
        Fixed error in new version of TermSet creation
        Added workaround for known PnP-NewTermSet CmdLet bug, where it sometimes fails if LCID is not provided. Defaulting to Classic Intranet SiteCollection localeid in JSON file.
        RedisCacheTLS 1.2 added as Wizdom App Service App Setting in the rediscache ARM template
    v2.16
        Supporting '-' in UPNs
        Removed -Limit ALL in get-spositegroup as it's not supported
        added -all parameter to Get-MsolServicePrincipal to account for gazillions of sharepoint apps
        Adding support for 503 errors from SharePoint in 'Checking Application Catalog rights'
        Added better error message when Teams Service Administrator role activation fails
        Added better error message for New-PnPTermGroup failure
        Added error message and exit when tenant name has not been configured. Yes, that happens...
        Added -all parameter to get-azureadapplication to account for gazillions of sharepoint apps
        Check of SQL server name now checks for lowercase only.

.LINK
    Updated versions of this script will be included in the Wizdom Source File packages that can be downloaded from the Wizdom Partner Portal
#>
[cmdletbinding(SupportsShouldProcess=$True)]
param (
    [Parameter(Mandatory=$true)]
    [String]$configFile, 
    [Parameter(Mandatory=$true, ParameterSetName="AzureMFA_SPMFA")]
    [Parameter(Mandatory=$true, ParameterSetName = "AzureNonMFA_SPMFA")]
    [switch]$UseWebLoginSharePoint,
    [Parameter(Mandatory=$true, ParameterSetName="AzureMFA_SPNonMFA")]
    [Parameter(Mandatory=$true, ParameterSetName = "AzureNonMFA_SPNonMFA")]
    [string]$SharePointTenantAdmin,
    [Parameter(Mandatory=$false, ParameterSetName="AzureMFA_SPNonMFA")]
    [Parameter(Mandatory=$false, ParameterSetName = "AzureNonMFA_SPNonMFA")]
    [string]$SharePointTenantAdminPassword,
    [Parameter(Mandatory=$true, ParameterSetName = "AzureMFA_SPMFA")]
    [Parameter(Mandatory=$true, ParameterSetName = "AzureMFA_SPNonMFA")]
    [switch]$UseWebLoginAzure,
    [Parameter(Mandatory=$true, ParameterSetName = "AzureNonMFA_SPMFA")]
    [Parameter(Mandatory=$true, ParameterSetName = "AzureNonMFA_SPNonMFA")]
    [string]$azureSubscriptionAdmin,
    [Parameter(Mandatory=$false, ParameterSetName = "AzureNonMFA_SPMFA")]
    [Parameter(Mandatory=$false, ParameterSetName = "AzureNonMFA_SPNonMFA")]
    [string]$azureSubscriptionAdminPassword,
    [Parameter(Mandatory=$false)]
    [string]$wizdomLicensePortalUser,
    [Parameter(Mandatory=$false)]
    [string]$wizdomLicensePortalUserPassword,
    [Parameter(Mandatory=$false)]
    [switch]$skipCheckPrerequisites,
    [Parameter(Mandatory=$false)]
    [ValidateSet('Basic', 'WithAppInsights', 'WithMobileSolution', 'WithAppInsightsAndMobileSolution', 'NoneTestConfig')]$installMode = "Basic",
    [Parameter(Mandatory=$false)]
    [ValidateSet('AzureResourceGroup', 'AzureResourceGroup_SharePointSites', 'AzureResourceGroup_SharePointSites_Office365Users', 'AzureResourceGroup_Office365Users', 'AzureServices', 'AzureServices_SharePointSites', 'AzureServices_SharePointSites_Office365Users', 'AzureServices_Office365Users')]$removeMode,
    [Parameter(Mandatory=$true)]
    [ValidateSet('Classic', 'Modern', 'Both')]$environment,
    [Parameter(Mandatory=$false)]
    [String]$outputSettingsFile,
    [Parameter(Mandatory=$false)]
    [Switch]$ConfirmRemove = $true
    
)

if (($PSVersionTable.PSVersion.Major -lt 5) -or (($PSVersionTable.PSVersion.Major -eq 5) -and ($PSVersionTable.PSVersion.Minor -lt 1))) {
    Write-Host "PowerShell version 5.1 or greater is required to run this script." -ForegroundColor Red
    Write-Host "Please find the download packages here: https://docs.microsoft.com/en-us/powershell/scripting/install/installing-windows-powershell?view=powershell-6#upgrading-existing-windows-powershell"
    Exit
}

Write-Host ("Installation starttime: " + (get-date)) -ForegroundColor White

#region Install or Update required PowerShell Modules and components
    if (-not $skipCheckPrerequisites.IsPresent) {
        $moduleList = @("AzureRM", "Azure","MSOnline", "SharePointPnPPowerShellOnline", "Microsoft.Online.SharePoint.PowerShell", "AzureAD")
        Remove-module *
        foreach ($module in $moduleList) {
            $moduleOnline = Find-Module -Name $module
            $moduleInstalled = @(Get-Module -ListAvailable -Name $module)
            if (-not $moduleInstalled -and $module -eq "AzureAD") {
                $moduleInstalled = @(Get-Module -ListAvailable -Name "AzureADPreview")
                if ($moduleInstalled) {
                    $module = "AzureADPreview"
                }
            }

            if ($moduleInstalled) {
	            if ($moduleInstalled[0].Version -eq $moduleOnline.Version) {
		            Write-Host "$($module) module is installed and latest version" -foregroundcolor green
	            } else {
                    Write-Host
                    $inputString = "Confirm`n`r" + $module + " module is currently version " + $moduleInstalled[0].Version.ToString() + ". The latest version is " + $moduleOnline.Version.ToString() + " - do you want to Update?`n`rThe process will open in a new window to instal as 'admin'`r`n[Y] Yes  [N] No (default is 'Y')"
                    $input = Read-Host -prompt $inputString
                    if ($input -eq $null) {$input = "y"}
                    if ($input.ToString().ToLower() -ne "n") { 
                        start-process powershell -Verb RunAs "try {Install-Module $module -AllowClobber -Force} catch {Install-Module $module -AllowClobber -Force -SkipPublisherCheck}" -wait
                    }
	            }
            } else {
                Write-Host
                $inputString = "Confirm`n`r" + $module + " module is not installed. Do you want to Install?`n`rThe process will open in a new window to instal as 'admin'`r`n[Y] Yes  [N] No (default is 'Y')"
                $input = Read-Host -prompt $inputString
                if ($input -eq $null) {$input = "y"}
                if ($input.ToString().ToLower() -ne "n") { 
    		        start-process powershell -Verb RunAs "try {Install-Module $module -AllowClobber -Force} catch {Install-Module $module -AllowClobber -Force -SkipPublisherCheck}" -Wait
                } else {
                    Write-Host "Module prerequisites not met. Exiting installation script."
                    Exit
                }
            }
        }

        $wmiObjects = @(Get-WmiObject -Class Win32_Product)

        if (($wmiObjects.where({$_.Name -eq 'Node.js'})).count -eq 0) {
            Write-Host "NodeJS not installed" -ForegroundColor Yellow
            
            Write-Host "Downloading NodeJS..."
            $msiFile = "node-v8.12.0.msi"
            if ([Environment]::Is64BitOperatingSystem) {
                $fileUrl = "https://nodejs.org/dist/v8.12.0/node-v8.12.0-x64.msi"
            } else {
                $fileUrl = "https://nodejs.org/dist/v8.12.0/node-v8.12.0-x86.msi"
            }
            Invoke-WebRequest -uri $fileUrl -OutFile ($env:TEMP + "\" + $msiFile) -UseBasicParsing
            
            Write-Host "Intalling NodeJS..."
            Start-Process -FilePath "msiexec.exe" -ArgumentList "/i $msiFile /norestart" -WorkingDirectory $env:TEMP -Wait

            $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User") 

            Remove-Item -Path ($env:TEMP + "\" + $msiFile) -Force -ErrorAction Ignore
        } else {
            Write-Host "NodeJS and npm is installed" -ForegroundColor Green
        }

        if (($wmiObjects.where({$_.Name -eq 'Microsoft Online Services Sign-in Assistant'})).count -eq 0) {
            Write-Host "Microsoft Online Services Sign-in Assistant not installed" -ForegroundColor Yellow
            if ([Environment]::Is64BitOperatingSystem) {
                $fileUrl = "http://download.microsoft.com/download/7/1/E/71EF1D05-A42C-4A1F-8162-96494B5E615C/msoidcli_64bit.msi"
            } else {
                $fileUrl = "http://download.microsoft.com/download/7/1/E/71EF1D05-A42C-4A1F-8162-96494B5E615C/msoidcli_32bit.msi"
            }
            
            Write-Host "Downloading Microsoft Online Services Sign-in Assistant..." 
            $msiFile = "msoidcli.msi"
            Invoke-WebRequest -uri $fileUrl -OutFile ($env:TEMP + "\" + $msiFile) -UseBasicParsing
            
            Write-Host "Intalling Microsoft Online Services Sign-in Assistant..."
            
            Start-Process -FilePath "msiexec.exe" -ArgumentList "/i $msiFile /norestart" -WorkingDirectory $env:TEMP -Wait

            Remove-Item -Path ($env:TEMP + "\" + $msiFile) -Force -ErrorAction Ignore
        } else {
            Write-Host "Microsoft Online Services Sign-in Assistant is installed" -ForegroundColor Green
        }

        if (($wmiObjects.where({$_.Name -like 'Microsoft Web Deploy*'})).count -eq 0) {
            Write-Host "Microsoft Web Deploy not installed." -ForegroundColor Yellow
            if ([Environment]::Is64BitOperatingSystem) {
                $fileUrl = "http://download.microsoft.com/download/0/1/D/01DC28EA-638C-4A22-A57B-4CEF97755C6C/WebDeploy_amd64_en-US.msi"
            } else {
                $fileUrl = "http://download.microsoft.com/download/0/1/D/01DC28EA-638C-4A22-A57B-4CEF97755C6C/WebDeploy_x86_en-US.msi"
            }
            
            Write-Host "Downloading Microsoft Web Deploy 3.6..." 
            $msiFile = "WebDeploy.msi"
            Invoke-WebRequest -uri $fileUrl -OutFile ($env:TEMP + "\" + $msiFile) -UseBasicParsing
            
            Write-Host "Intalling Microsoft Web Deploy 3.6..."
            Start-Process -FilePath "msiexec.exe" -ArgumentList "/i $msiFile /norestart" -WorkingDirectory $env:TEMP -Wait

            Remove-Item -Path ($env:TEMP + "\" + $msiFile) -Force -ErrorAction Ignore
        } else {
            Write-Host "Microsoft Web Deploy 3.6 is installed" -ForegroundColor Green
        }

    }
#endregion

#region Base variables
    Import-Module ($PSScriptRoot + "\SharePointOnlineInstallationSupportFiles\SharePointOnlineInstallationFunctions.psm1") -Force -DisableNameChecking
    try {
        #$config = (Get-Content -Raw -Path $configFile -Force) -replace '(?ms)/\*.*?\*/', '' | ConvertFrom-Json
        $config = (Get-Content -Raw -Path $configFile -Force) -replace "(?ms)/\*(?>(?:(?!\*/|/\*).)*)(?>(?:/\*(?>(?:(?!\*/|/\*).)*)\*/(?>(?:(?!\*/|/\*).)*))*).*?\*/|--.*?\r?[\n]", '' | ConvertFrom-Json
    } catch {
        Write-Host "Invalid JSON file: $($configfile)" -ForegroundColor Red
        Exit
    }

    if ($config.Office365.TenantName -eq "<tenantname>") {
        Write-Host "The tenantname has not been configured in the JSON file (Path: Office365.TenantName)" -ForegroundColor Red
        Exit
    }

    if ($config.Office365.TenantName -match "^https?://(.*?\.)+?") {
        $config.Office365.TenantName = $config.Office365.TenantName.split("/")[2].split(".")[0]
    }
    $SharePointAdminUrl = "https://" + $config.Office365.TenantName + "-admin.sharepoint.com"
    $SharePointUrl = "https://" + $config.Office365.TenantName + ".sharepoint.com"

    if ($config.SharePointClassic.IntranetSiteCollection.InstallOnRootSite) {
        $wizdomSiteSPO = $sharePointUrl
    } else {
        $wizdomSiteSPO = $sharePointUrl + "/sites/" + $config.SharePointClassic.IntranetSiteCollection.Url
    }

    $outputMaxLength = 10 + (77 + $config.Office365.TenantName.Length + $config.SharePointClassic.IntranetSiteCollection.Url.Length)
    #Could be longer: "Processing the search user '" + $config.Office365.WizdomSearchUser.UserName + "'" + " - User already exists, assign an Office 365 F1 License as a minimum if Teams will be used"
    $i = 10 + 120 + $config.Office365.WizdomSearchUser.Account.Length
    if ($i -gt $outputMaxLength) {$outputMaxLength = $i}
    #Longest static message is currently "Creating default.aspx page in the pages library..."
    if ($outputMaxLength -le 132) {$outputMaxLength = 132}
    $waitVeryLong = 60
    $waitLong = 10
    $waitShort = 5
    
    $outFileArrayBasic = @()
    $outFileArrayCourseManagement = @()
    $outFileArrayMobileApp = @()
    $outFileArrayAnalytics = @()
    $outFileArrayConnectionStrings = @()

    $tenantAdminPasswordProfile = New-Object -TypeName Microsoft.Open.AzureAD.Model.PasswordProfile
    $tenantAdminPasswordProfile.ForceChangePasswordNextLogin = $false
    
    if (-not ($config.Azure.companyname -match '^[A-Z][A-Z0-9-]{2,23}$')) {
        Write-Host "Azure Companyname is not valid" -ForegroundColor Red
        exit
    } else {
        $websiteName = ($config.Wizdom.websiteName).replace('##companyname##',$config.Azure.companyname)
        $websiteUrl = "https://" + $websiteName.tolower() + ".azurewebsites.net"
    }

#endregion

#region Sign in to Office 365
    try {
        if ($UseWebLoginSharePoint.IsPresent) {
            $output = Write-WizdomHost -messageType PROCESSATTENTION -message "MsolService-Module: Sign in to Office 365 with admin rights for AAD" -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
            Connect-MsolService|write-verbose
        } else {
            $output = Write-WizdomHost -messageType PROCESS -message ("Logging in to Office 365 Azure AD v.1 Service as " + $SharePointTenantAdmin)
                if ($SharePointTenantAdminPassword -eq "") {
                    Write-Host
                    $SPOsecurePassword = Read-Host -Prompt ("Please enter the password for " + $SharePointTenantAdmin) -AsSecureString
                } else {$SPOsecurePassword = $SharePointTenantAdminPassword | ConvertTo-SecureString -AsPlainText -Force}
                $credentialsOffice365Admin = New-Object System.Management.Automation.PSCredential($SharePointTenantAdmin,$SPOsecurePassword)
        
            Connect-MsolService -Credential $credentialsOffice365Admin|write-verbose
        } 
        Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    } catch {
        Write-WizdomHost -messageType ERROR -outputMaxLength $outputMaxLength -initialStringLength $output -afterOutputMessage "Cannot login. Password or username incorrect. Exiting script"
        Exit
    }
#endregion

#region Registering Wizdom App on SharePoint Online
    $clientId = [System.Guid]::NewGuid()
    $bytes = New-Object Byte[] 32
    $rand = [System.Security.Cryptography.RandomNumberGenerator]::Create()
    $rand.GetBytes($bytes)
    $rand.Dispose()
    $clientSecret = [System.Convert]::ToBase64String($bytes)
    $enddate = [DateTime]::Now.AddYears(3).AddDays(-1)
    $startdate = [DateTime]::Now
    $appDomain = $websiteName + ".azurewebsites.net"
    $servicePrincipalName = @("$clientId/$appDomain")
#            Connect-MsolService -Credential $credentialsOffice365Admin
    if ($PSCmdlet.ShouldProcess($servicePrincipalName, "Create Service Principal")) {
        $oldSharepointApps = Get-MsolServicePrincipal -All | Where DisplayName -eq $config.SharePointClassic.WizdomApp.Name
        if ($oldSharepointApps -ne $null) {
            try {
            $output = Write-WizdomHost -messageType PROCESS -message ("Removing old existing SharePoint App from App Catalog") -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
                if ($PSCmdlet.ShouldProcess($config.SharePointClassic.WizdomApp.Title,"Remove App")) {
                    foreach ($oldSharepointApp in $oldSharepointApps) {
                        Remove-MsolServicePrincipal -ObjectId $oldSharepointApp.ObjectId -ErrorAction SilentlyContinue|Write-Verbose
                    }
                }
            Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
            } catch {
                Write-WizdomHost -messageType ERROR -outputMaxLength $outputMaxLength -initialStringLength $output -afterOutputMessage $_.Exception.Message 
            }
        }
        $output = Write-WizdomHost -messageType PROCESS -message "Registering Wizdom App on SharePoint Online" -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
            New-MsolServicePrincipal -ServicePrincipalNames $servicePrincipalName -AppPrincipalId $clientId -DisplayName $config.SharePointClassic.WizdomApp.Name -Addresses (New-MsolServicePrincipalAddresses -Address $websiteUrl) -Value $clientSecret|write-verbose #-value not necessary. Added to get rid of "The following symmetric key was created as one was not supplied" message.

            New-MsolServicePrincipalCredential -AppPrincipalId $clientId -Type Symmetric -Usage Sign -Value $clientSecret -StartDate $startdate -EndDate $enddate|write-verbose
            New-MsolServicePrincipalCredential -AppPrincipalId $clientId -Type Symmetric -Usage Verify -Value $clientSecret -StartDate $startdate -EndDate $enddate|write-verbose
            New-MsolServicePrincipalCredential -AppPrincipalId $clientId -Type Password -Usage Verify -Value $clientSecret -StartDate $startdate -EndDate $enddate|write-verbose
        Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    }
    
#endregion

#region Updating local Puppeteer / Chromium installation
    $output = Write-WizdomHost -messageType PROCESS -message "Updating local Puppeteer / Chromium installation" -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    if ($PSCmdlet.ShouldProcess("Local Puppeteer/Chromium installation", "Update")) {    
        Start-Process -FilePath "npm" -ArgumentList "update" -WorkingDirectory ($PSScriptRoot + $config.Wizdom.nodejsWorkingDirectory) -Wait
    }
    Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
#endregion

#region Setting App Trust
        $nodejsDir = Join-Path -Path $env:ProgramFiles -ChildPath nodejs
        $cmd = 'node'

    if ($UseWebLoginSharePoint.IsPresent) {
        $output = Write-WizdomHost -messageType PROCESSATTENTION -message "Setting App Trust: login to Office 365 with SharePoint admin rights" -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
        $puppeteerScript = $PSScriptRoot + $config.Wizdom.appTrustPuppeteerScriptMFA + ' ' + $SharePointUrl + ' ' + $SharePointAdminUrl + ' ' + $clientId
    } else {
        $output = Write-WizdomHost -messageType PROCESS -message "Setting App Trust" -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
        $puppeteerScript = $PSScriptRoot + $config.Wizdom.appTrustPuppeteerScript + ' ' + $SharePointUrl + ' ' + $SharePointAdminUrl + ' ' + $clientId + ' ' + $SharePointTenantAdmin + ' ' + $credentialsOffice365Admin.GetNetworkCredential().Password
    }
    $inner = "$puppeteerScript -NoNewWindow -Command cd '$nodejsDir'"
    if ($PSCmdlet.ShouldProcess($config.SharePointClassic.WizdomApp.Title, "Trust Wizdom App Package")) {
        Start-Process $cmd -ArgumentList $inner -Wait
    }
    Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
#endregion

#region Test if SharePoint trust has been established
    $output = Write-WizdomHost -messageType PROCESS -message "Testing if SharePoint App Trust has been established successfully" -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    try {
        $appOnlyContext = connect-pnponline -ReturnConnection -AppId $clientId -AppSecret $clientSecret -Url ("https://" + $config.Office365.TenantName + ".sharepoint.com/") -ErrorAction SilentlyContinue
        $trustTest = Get-PnPWeb -Connection $appOnlyContext -ErrorAction SilentlyContinue
    } catch {
        $trustTest = $null
    }
    if ($trustTest -eq $null) {
        Write-WizdomHost -messageType ERROR -outputMaxLength $outputMaxLength -initialStringLength $output -afterOutputMessage "Automated App trust has failed." -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
        Write-Host
        Write-Host "Please perform the following manual steps:" -ForegroundColor Yellow
        Write-Host "- Navigate to '$($SharePointAdminUrl+'/_layouts/15/appinv.aspx')' and login using '$($SharePointTenantAdmin)'"
        Write-Host "- Search for the App Id: '$($clientId)'"
        Write-Host "- Insert the following XML in the input box for ""Permission Request XML"""
        Write-Host "------------------------------------------------"
        Write-Host "<AppPermissionRequests AllowAppOnlyPolicy=""true"" >"
        Write-Host "<AppPermissionRequest Scope=""http://sharepoint/content/sitecollection"" Right=""FullControl"" />"
        Write-Host "<AppPermissionRequest Scope=""http://sharepoint/social/tenant"" Right=""FullControl"" />"
        Write-Host "<AppPermissionRequest Scope=""http://sharepoint/search"" Right=""QueryAsUserIgnoreAppPrincipal"" />"
        Write-Host "<AppPermissionRequest Scope=""http://sharepoint/content/tenant"" Right=""FullControl"" />"
        Write-Host "<AppPermissionRequest Scope=""http://sharepoint/taxonomy"" Right=""Write"" />"
        Write-Host "<AppPermissionRequest Scope=""http://sharepoint/content/sitecollection/web"" Right=""FullControl"" />"
        Write-Host "</AppPermissionRequests>"
        Write-Host "------------------------------------------------"
        Write-Host "- Click on ""Create"""
        Write-Host "- Click on ""Trust It"""
        pause ("Press any key to continue...")
        Write-Host
    } else {
        Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    }
#endregion

#region SharePoint Tenant Admin Password input        
    $userOK = $false
    do {
        try {
                $output = Write-WizdomHost -messageType PROCESS -message "PnPOnline-Module: Signing in to SharePoint Admin as SharePoint App"
                $pnpSharePointAdminAppContext = Connect-PnPOnline -ReturnConnection -Url $SharePointAdminUrl -AppId $clientId -AppSecret $clientSecret -ErrorAction Stop
                Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output
            $userOK = $true
        } catch {
            write-host $_ -ForegroundColor Red
        }
    } until ($userOK)
    
#endregion

#region Sign in to Azure AD
    try {
        if ($UseWebLoginSharePoint.IsPresent) {
            $output = Write-WizdomHost -messageType PROCESSATTENTION -message "AzureAD-Module: Sign in to Office 365 with admin rights for AAD"
            $azureADContext = Connect-AzureAD -WhatIf:$false|write-verbose
            $SharePointTenantAdmin = (Get-AzureADCurrentSessionInfo).Account.Id
        } else {
            $output = Write-WizdomHost -messageType PROCESS -message ("Logging in to Office 365 Azure AD v.2 Service as " + $credentialsOffice365Admin.UserName)
            $azureADContext = Connect-AzureAD -Credential $credentialsOffice365Admin -WhatIf:$false|write-verbose
        }
    } catch {
        Write-WizdomHost -messageType ERROR -outputMaxLength $outputMaxLength -initialStringLength $output -afterOutputMessage "Cannot login. Password or username incorrect. Exiting script" 
        Exit
    }
    if ((Get-AzureADDirectoryRoleMember -ObjectId (Get-AzureADDirectoryRole).where({$_.DisplayName -eq "Company Administrator"}).objectid).where({$_.UserPrincipalName -eq $azureAdContext.Account}) -eq $null) {
        Write-WizdomHost -messageType ERROR -outputMaxLength $outputMaxLength -initialStringLength $output -afterOutputMessage ($SharePointTenantAdmin + "does not have sufficient (Company Administrator) rights on the tenant. Exiting script")
        Exit
    } else {
        Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output
    }
    $tenantIdOffice365 = ((Invoke-WebRequest -uri "https://login.microsoftonline.com/$((get-azureaddomain).where({$_.Name -like ($config.Office365.TenantName + ".onmicrosoft.*")}).Name)/.well-known/openid-configuration" -UseBasicParsing).Content | ConvertFrom-Json).authorization_endpoint.split('/')[3]

#endregion

#region Sign in to Office 365 Azure
    if ($UseWebLoginSharePoint.IsPresent) {
        $output = Write-WizdomHost -messageType PROCESSATTENTION -message "AzureRM-Module: Sign in to Office 365 with tenant admin rights"
        $o365Context = Login-AzureRmAccount -TenantId $tenantIdOffice365 -WhatIf:$false
        Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    } else {$o365Context = Login-AzureRmAccount -TenantId $tenantIdOffice365 -Credential $credentialsOffice365Admin -WhatIf:$false}

    $o365Token = $o365Context.Context.TokenCache.ReadItems().where({($_.tenantid -eq $tenantIdOffice365) -and ($_.displayableid -eq $o365Context.Context.Account) -and $_.resource -eq "https://management.core.windows.net/"})[-1].AccessToken
    $refreshToken = $o365Context.Context.TokenCache.ReadItems().where({($_.tenantid -eq $tenantIdOffice365) -and ($_.displayableid -eq $o365Context.Context.Account)})[-1].refreshtoken

    #Doesn't get token when not using azurermlogin
    $body = "grant_type=refresh_token&refresh_token=$($refreshToken)&resource=74658136-14ec-4630-ad9b-26e160ff0fc6"
    $o365ApiToken = Invoke-RestMethod "https://login.windows.net/$tenantIdOffice365/oauth2/token" -Method POST -Body $body -ContentType 'application/x-www-form-urlencoded'

#endregion



#region Logging in to SharePoint Admin

    try {
        if (-not $useWebLoginSharePoint.IsPresent) {
            $output = Write-WizdomHost -messageType PROCESS -message ("Logging in to SharePoint Admin as " + $SharePointTenantAdmin)
                Connect-SPOService -Url $SharePointAdminUrl -Credential $credentialsOffice365Admin|write-verbose
                $pnpSharePointAdminContext = Connect-PnPOnline -ReturnConnection -Url $SharePointAdminUrl -Credentials $credentialsOffice365Admin | Write-Verbose
            Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
        } else {
            $output = Write-WizdomHost -messageType PROCESSATTENTION -message "SPOService-Module: Sign in to Office 365 with SharePoint Admin rights"
                Connect-SPOService -Url $SharePointAdminUrl |write-verbose
            Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference

            $output = Write-WizdomHost -messageType PROCESSATTENTION -message "PnPOnline-Module: Sign in to Office 365 with SharePoint Admin rights"
                $pnpSharePointAdminContext = Connect-PnPOnline -ReturnConnection -Url $SharePointAdminUrl -UseWebLogin | Write-Verbose
            Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
        }
    } catch {
        Write-WizdomHost -messageType ERROR -outputMaxLength $outputMaxLength -initialStringLength $output -afterOutputMessage "Cannot login. Insufficient rights? Exiting script"
        Exit
    }



#endregion

#region Logging in to Azure Subscription
    $userOK = $false
    do {
        
        if ($UseWebLoginAzure.IsPresent) {
            $output = Write-WizdomHost -messageType PROCESSATTENTION -message "AzureRM-Module: Log in to Azure with Owner rights for the Subscription to host Wizdom"
            $azureContext = Login-AzureRmAccount -Subscription $config.Azure.SubscriptionId -WhatIf:$false
            $azureSubscriptionAdminUsr = $azureContext.Context.Account.Id
            $userOK = $true
        } else {
            $azureSubscriptionAdminUsr = $azureSubscriptionAdmin
            if ($azureSubscriptionAdmin -eq $SharePointTenantAdmin) {
                $AzureSecurePassword = $SPOsecurePassword
            } else {
                if ($azureSubscriptionAdminPassword -eq "") {
                    Write-Host
                    $AzureSecurePassword = Read-Host -Prompt ("Please enter the password for " + $AzureSubscriptionAdmin) -AsSecureString
                } else {$AzureSecurePassword = $azureSubscriptionAdminPassword | ConvertTo-SecureString -AsPlainText -Force}
            }

            $output = Write-WizdomHost -messageType PROCESS -message ("Logging in to Azure as " + $azureSubscriptionAdminUsr)
            
            $credentialAzureSubscriptionAdmin = New-Object System.Management.Automation.PSCredential($azureSubscriptionAdmin,$AzureSecurePassword)
            try {
                $azureContext = Login-AzureRmAccount -Subscription $config.Azure.SubscriptionId -Credential $credentialAzureSubscriptionAdmin -ErrorAction Stop -WhatIf:$false
                $userOK = $true
            } catch {
                Write-Host
                write-host "Authentication Error: Bad username or password." -ForegroundColor Red
            }
        }
    } until ($userOK)

    Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output

#endregion

#region Get Wizdom License Portal User password
    if ($wizdomLicensePortalUser -ne "" -and $config.Wizdom.LicenseID -ne "") {
        if (($wizdomLicensePortalUser -eq $SharePointTenantAdmin) -and (-not $UseWebLoginSharePoint.IsPresent)) {
            $WizdomLicensePortalSecurePassword = $SPOsecurePassword
        } elseif (($wizdomLicensePortalUser -eq $azureSubscriptionAdmin) -and (-not $UseWebLoginAzure.IsPresent)) {
            $WizdomLicensePortalSecurePassword = $AzureSecurePassword
        } else {
            if ($wizdomLicensePortalUserPassword -eq "") {
                $WizdomLicensePortalSecurePassword = Read-Host -Prompt ("Please enter the password for " + $wizdomLicensePortalUser) -AsSecureString
            } else {$WizdomLicensePortalSecurePassword = $wizdomLicensePortalUserPassword | ConvertTo-SecureString -AsPlainText -Force}
        }
        $credentialWizdomLicensePortal = New-Object System.Management.Automation.PSCredential($wizdomLicensePortalUser,$WizdomLicensePortalSecurePassword)

    }

#endregion

#region Check Config values and generate derived values


    if ($removeMode -ne $null) {
        $output = "Verifying config file settings and removing existing installation or remnants thereof"
        Write-Host $output -ForegroundColor DarkGray
    } else {
        $output = Write-WizdomHost -messageType PROCESS -message "Verifying config file settings" -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    }
        $outputConfigCheck = ""
        $outputErrorCount = 0
        $testTermGroupName = new-guid
        $termGroupCreationResult = New-PnPTermGroup -Name $testTermGroupName -Id $testTermGroupName -Connection $pnpSharePointAdminContext -ErrorAction SilentlyContinue
        if ($termGroupCreationResult -eq $null) {
            $outputErrorCount++
            $outputConfigCheck +=  "$($outputErrorCount)) The user $($Username) doesn't seem to be Term Store Administrator in SharePoint Online Admin. Please add the user and rerun the installation script.`r`n"
        } else {
            Start-Sleep -Seconds $waitShort
            Remove-PnPTermGroup -GroupName $testTermGroupName -Force -Connection $pnpSharePointAdminContext
        }            

        try {Invoke-WebRequest -Uri ("https://" + $config.Office365.TenantName + ".sharepoint.com") -Method Head -UseBasicParsing|write-verbose} catch {
            $outputErrorCount++
            $outputConfigCheck += "$($outputErrorCount)) Tenant non-existing`r`n"
        }

        if ($config.SharePointClassic.IntranetSiteCollection.Url -match "^https?://([\w-]*?\.)+[\w-]*?/[\w-]*?/[\w-]*$") {
            $config.SharePointClassic.IntranetSiteCollection.Url = $config.SharePointClassic.IntranetSiteCollection.Url.split("/")[-1]
        }
                
        if ($outputConfigCheck -eq "") {
            if ($environment -eq 'Classic' -or $environment -eq 'Both') {
                if ((-not ($config.SharePointClassic.IntranetSiteCollection.Url -match "^[\w-]*$")) -and -not $config.SharePointClassic.IntranetSiteCollection.InstallOnRootSite) {
                    $outputErrorCount++
                    $outputConfigCheck += "$($outputErrorCount)) SiteCollection Url is missing or incorrect`r`n"
                } else {
                    if ($config.SharePointClassic.IntranetSiteCollection.Title -eq "") {$config.SharePointClassic.IntranetSiteCollection.Title = $config.SharePointClassic.IntranetSiteCollection.Url}
                    if ((get-sposite -Limit ALL| Where Url -eq $wizdomSiteSPO) -ne $null ) {
                        if ($removeMode -match "SharePointSites" -and -not $config.SharePointClassic.IntranetSiteCollection.InstallOnRootSite) {
                            $output = Write-WizdomHost -messageType PROCESS -message ("Removing SharePoint Site " + $config.SharePointClassic.IntranetSiteCollection.Title) -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
                            try {
                                if ($PSCmdlet.ShouldProcess($wizdomSiteSPO, "Remove Site")) {
                                    if ($ConfirmRemove) {
                                        Remove-SPOSite -Identity $wizdomSiteSPO -ErrorAction SilentlyContinue |write-verbose
                                    } else {
                                        Remove-SPOSite -Identity $wizdomSiteSPO -Confirm:$false -ErrorAction SilentlyContinue |write-verbose
                                    }
                                    if ($PSCmdlet.ShouldProcess($config.SharePointClassic.IntranetSiteCollection.Title,"Waiting for site removal")) {
                                        $siteDeleted = $false
                                        do {
                                            Start-Sleep -Seconds $waitLong
                                            try {
                                                $site = Get-SPODeletedSite -Identity $wizdomSiteSPO -ErrorAction Stop
                                                $siteDeleted = $true}
                                            catch {$siteDeleted = $false}
                                        } until ($siteDeleted)
                                    }
                                    if ($ConfirmRemove) {
                                        Remove-SPODeletedSite -Identity $wizdomSiteSPO -ErrorAction SilentlyContinue
                                    } else {
                                        Remove-SPODeletedSite -Identity $wizdomSiteSPO -Confirm:$false -ErrorAction SilentlyContinue
                                    }
                                }
                                Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
                            } catch {
                                Write-WizdomHost -messageType WARNING -outputMaxLength $outputMaxLength -initialStringLength $output -afterOutputMessage $_.Exception.Message -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
                            }
                        } else {
                            if ((get-pnpappinstance -Connection (Connect-PnPOnline -Url $wizdomSiteSPO  -AppId $clientId -AppSecret $clientSecret -ReturnConnection) -WarningAction Ignore).where({$_."title" -eq $config.SharePointClassic.WizdomApp.Title}).Title -ne $null) {
                                $outputErrorCount++
                                $outputConfigCheck += "$($outputErrorCount)) The SharePoint App " + $config.SharePointClassic.WizdomApp.Title + " already exists on web " + $wizdomSiteSPO + "`r`n"
                            }
                        }
                    }
                    if (-not (get-azureaduser -objectid $config.SharePointClassic.IntranetSiteCollection.Owner -ErrorAction SilentlyContinue)) {
                        $outputErrorCount++
                        $outputConfigCheck += "$($outputErrorCount)) SiteCollection Owner not existing in AD`r`n"
                    }
                }
            }
            #MobileApp
            if ($installMode -match "MobileSolution") {
                if (-not ($config.MobileApp.SharePoint.Url -match "^[\w-]*$")) {
                    $outputErrorCount++
                    $outputConfigCheck += "Mobile SiteCollection Url is missing or incorrect`r`n"
                } else {
                    if ($config.MobileApp.SharePoint.Title -eq "") {$config.MobileApp.SharePoint.Title = $config.MobileApp.SharePoint.Url}
                }
            }
        }
        if (-not ($config.SharePointClassic.IntranetSiteCollection.LocaleId -match "^[0-9]{4}$")) {
            $outputErrorCount++
            $outputConfigCheck += "$($outputErrorCount)) SiteCollection LocaleId invalid`r`n"
        }
        #mobileapp
        if ($installMode -match "MobileSolution") { 
            if (-not ($config.MobileApp.SharePoint.LocaleId -match "^[0-9]{4}$")) {
                $outputErrorCount++
                $outputConfigCheck += "$($outputErrorCount)) Mobile SiteCollection LocaleId invalid`r`n"
            }
        }
        if ($config.SharePointClassic.WizdomDataSiteCollection -eq "" -and $config.SharePointClassic.IntranetSiteCollection.InstallOnRootSite) {
            $outputErrorCount++
            $outputConfigCheck += "$($outputErrorCount)) Wizdom Data SiteCollection url is a reuqired field when installing Wizdom on the root SiteCollection`r`n"
        } elseif ($config.SharePointClassic.WizdomDataSiteCollection -match "^https?://([\w-]*?\.)+[\w-]*?/[\w-]*?/[\w-]*$") {
            $wizdomDataSiteSPO = $config.SharePointClassic.WizdomDataSiteCollection
        } elseif ($config.SharePointClassic.WizdomDataSiteCollection -ne $null -and $config.SharePointClassic.WizdomDataSiteCollection -ne "") {
            $wizdomDataSiteSPO = $sharePointUrl + "/sites/" + $config.SharePointClassic.WizdomDataSiteCollection
        } else {
            $wizdomDataSiteSPO = $sharePointUrl + "/sites/" + $config.SharePointClassic.IntranetSiteCollection.Url + 'Data'
        }
        try {
            if ((get-sposite -Limit ALL| Where Url -eq $wizdomDataSiteSPO) -ne $null ) {
                if ($removeMode -match "SharePointSites" -and -not $config.SharePointClassic.IntranetSiteCollection.InstallOnRootSite) {
                    try {
                        $output = Write-WizdomHost -messageType PROCESS -message ("Removing SharePoint Data Site " + $config.SharePointClassic.IntranetSiteCollection.Title + ' Data') -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
                            if ($PSCmdlet.ShouldProcess($wizdomDataSiteSPO, "Remove Site")) {
                                if ($ConfirmRemove) {
                                    Remove-SPOSite -Identity $wizdomDataSiteSPO -ErrorAction SilentlyContinue
                                } else {
                                    Remove-SPOSite -Identity $wizdomDataSiteSPO -ErrorAction SilentlyContinue -Confirm:$false |write-verbose
                                }
                                if ($PSCmdlet.ShouldProcess($config.SharePointClassic.IntranetSiteCollection.Title+'Data',"Waiting for site removal")) {
                                    $siteDeleted = $false
                                    do {
                                        Start-Sleep -Seconds $waitLong
                                        try {
                                            $site = Get-SPODeletedSite -Identity $wizdomDataSiteSPO -ErrorAction Stop
                                            $siteDeleted = $true}
                                        catch {$siteDeleted = $false}
                                    } until ($siteDeleted)
                                }
                                if ($ConfirmRemove) {
                                    Remove-SPODeletedSite -Identity $wizdomDataSiteSPO -ErrorAction SilentlyContinue
                                } else {
                                    Remove-SPODeletedSite -Identity $wizdomDataSiteSPO -ErrorAction SilentlyContinue -Confirm:$false
                                }
                            }
                    Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
                    } catch {
                        Write-WizdomHost -messageType WARNING -outputMaxLength $outputMaxLength -initialStringLength $output -afterOutputMessage $_.Exception.Message -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
                    }
                }
            }
        } catch {}

        #Site Collection Owners
        $usr = Get-AzureADUser -ObjectId $config.SharePointClassic.IntranetSiteCollection.Owner -ErrorAction SilentlyContinue
        if ($usr -eq $null) {
            $outputErrorCount++
            $outputConfigCheck += "$($outputErrorCount)) SiteCollection owner " + $config.SharePointClassic.IntranetSiteCollection.Owner + " doesn't exist.`r`n"
        }
        if ($installMode -match "MobileSolution") {
            $usr = Get-AzureADUser -ObjectId $config.MobileApp.SharePoint.Owner -ErrorAction SilentlyContinue
            if ($usr -eq $null) {
                $outputErrorCount++
                $outputConfigCheck += "$($outputErrorCount)) Mobile SiteCollection owner " + $config.MobileApp.SharePoint.Owner + " doesn't exist.`r`n"
            }
        }
        
        #Office 365 Group
        $outputConfigCheck = ""
        if (-not ($config.Office365.WizdomSearchUser.Account -match '^[^\.][\w_\.-]*[^\.]@([\w]*\.){1,2}[\w]{2,3}$')) {
            $outputErrorCount++
            $outputConfigCheck += "$($outputErrorCount)) WizdomSearchUser missing or not formated as UPN in config file`r`n"
        } else {
            try {$wizdomSearchUser = Get-AzureADUser -ObjectId $config.Office365.WizdomSearchUser.Account -ErrorAction SilentlyContinue} catch {}
            if ($wizdomSearchUser -ne $null -and $removeMode -match "Office365Users") {
                $output = Write-WizdomHost -messageType PROCESS -message "Removing Office365 user $($config.Office365.WizdomSearchUser.Account)" -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
                if ($PSCmdlet.ShouldProcess($config.Office365.WizdomSearchUser.Account, "Remove User")) {
                    if ($removeMode -notmatch "SharePointSites") {
                        if ((get-sposite -Limit ALL| Where Url -eq $wizdomDataSiteSPO) -ne $null ) {
                            remove-SPOUser -site $wizdomDataSiteSPO -LoginName $config.Office365.WizdomSearchUser.Account |Write-Verbose
                        }
                        if ((get-sposite -Limit ALL| Where Url -eq $wizdomSiteSPO) -ne $null ) {
                            remove-SPOUser -site $wizdomSiteSPO -LoginName $config.Office365.WizdomSearchUser.Account |Write-Verbose
                        }
                    }
                    Remove-AzureADUser -ObjectId $wizdomSearchUser.UserPrincipalName |Write-Verbose
                }
                $wizdomSearchUser = $null
                Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
            }
            if ($wizdomSearchUser -eq $null) {
                if ($config.Office365.WizdomSearchUser.Password -eq "") {$config.Office365.WizdomSearchUser.Password = Get-RandomPassword}
            } elseif ($config.Office365.WizdomSearchUser.Password -eq "") {
                $outputErrorCount++
                $outputConfigCheck += "$($outputErrorCount)) $($config.Office365.WizdomSearchUser.Account) already exists, but no password has been supplied`r`n"
            } else {
                try {
                    if (-not $UseWebLoginSharePoint.IsPresent) { #Only works for non-mfa enabled accounts
                        $credentialSearchUser = New-Object System.Management.Automation.PSCredential($config.Office365.WizdomSearchUser.Account,(ConvertTo-SecureString -String $config.Office365.WizdomSearchUser.Password -AsPlainText -Force))
                        Connect-PnPOnline -ReturnConnection -Url $SharePointUrl -Credentials $credentialSearchUser -ErrorAction Stop|write-verbose
                    }
                } catch {
                    $outputErrorCount++
                    $outputConfigCheck += "$($outputErrorCount)) Wrong username or password for the Wizdom Search User account: $($config.Office365.WizdomSearchUser.Account)`r`n"
                }
            }

        }
        if ($config.Office365.WizdomSearchUser.UserName -eq "") {$config.Office365.WizdomSearchUser.UserName = "Wizdom Search User"}
        if (-not (($config.Office365.WizdomSearchUser.UsageLocation).ToUpper() -match '^(AF|AX|AL|DZ|AS|AD|AO|AI|AQ|AG|AR|AM|AW|AU|AT|AZ|BS|BH|BD|BB|BY|BE|BZ|BJ|BM|BT|BO|BQ|BA|BW|BV|BR|IO|BN|BG|BF|BI|KH|CM|CA|CV|KY|CF|TD|CL|CN|CX|CC|CO|KM|CG|CD|CK|CR|CI|HR|CU|CW|CY|CZ|DK|DJ|DM|DO|EC|EG|SV|GQ|ER|EE|ET|FK|FO|FJ|FI|FR|GF|PF|TF|GA|GM|GE|DE|GH|GI|GR|GL|GD|GP|GU|GT|GG|GN|GW|GY|HT|HM|VA|HN|HK|HU|IS|IN|ID|IR|IQ|IE|IM|IL|IT|JM|JP|JE|JO|KZ|KE|KI|KP|KR|KW|KG|LA|LV|LB|LS|LR|LY|LI|LT|LU|MO|MK|MG|MW|MY|MV|ML|MT|MH|MQ|MR|MU|YT|MX|FM|MD|MC|MN|ME|MS|MA|MZ|MM|NA|NR|NP|NL|NC|NZ|NI|NE|NG|NU|NF|MP|NO|OM|PK|PW|PS|PA|PG|PY|PE|PH|PN|PL|PT|PR|QA|RE|RO|RU|RW|BL|SH|KN|LC|MF|PM|VC|WS|SM|ST|SA|SN|RS|SC|SL|SG|SX|SK|SI|SB|SO|ZA|GS|SS|ES|LK|SD|SR|SJ|SZ|SE|CH|SY|TW|TJ|TZ|TH|TL|TG|TK|TO|TT|TN|TR|TM|TC|TV|UG|UA|AE|GB|US|UM|UY|UZ|VU|VE|VN|VG|VI|WF|EH|YE|ZM|ZW)$')) {
            $outputErrorCount++
            $outputConfigCheck += "$($outputErrorCount)) WizdomSearchUser Countrycode invalid`r`n"
        }


        if (-not ($config.Office365.WizdomGraphUser.Account -match '^[^\.][\w_\.-]*[^\.]@([\w]*\.){1,2}[\w]{2,3}$')) {
            $outputErrorCount++
            $outputConfigCheck += "$($outputErrorCount)) WizdomGraphUser missing or not formated as UPN in config file`r`n"
        } else {
            if ($config.Office365.WizdomSearchUser.Account -ne $config.Office365.WizdomGraphUser.Account) {
                try{$WizdomGraphUser = Get-AzureADUser -ObjectId $config.Office365.WizdomGraphUser.Account -ErrorAction SilentlyContinue} catch {}
                if ($WizdomGraphUser -ne $null -and $removeMode -match "Office365Users") {
                    $output = Write-WizdomHost -messageType PROCESS -message "Removing Office365 user $($config.Office365.WizdomGraphUser.Account)" -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
                    if ($PSCmdlet.ShouldProcess($config.Office365.WizdomGraphUser.Account, "Remove User")) {
                        Remove-AzureADUser -ObjectId $WizdomGraphUser.UserPrincipalName |Write-Verbose
                    }
                    $WizdomGraphUser = $null
                    Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
                }
                if ($WizdomGraphUser -eq $null) {
                    if ($config.Office365.WizdomGraphUser.Password -eq "") {$config.Office365.WizdomGraphUser.Password = Get-RandomPassword}
                } elseif ($config.Office365.WizdomGraphUser.Password -eq "") {
                    $outputErrorCount++
                    $outputConfigCheck += "$($outputErrorCount)) $($config.Office365.WizdomGraphUser.Account) already exists, but no password has been supplied`r`n"
                } else {
                    try {
                        $credentialGraphUser = New-Object System.Management.Automation.PSCredential($config.Office365.WizdomGraphUser.Account,(ConvertTo-SecureString -String $config.Office365.WizdomGraphUser.Password -AsPlainText -Force))
                        Connect-PnPOnline -ReturnConnection -Url $SharePointUrl -Credentials $credentialGraphUser -ErrorAction Stop|write-verbose
                    } catch {
                        $outputErrorCount++
                        $outputConfigCheck += "$($outputErrorCount)) Wrong username or password for the Wizdom Graph User account: $($config.Office365.WizdomGraphUser.Account)`r`n"
                    }
                }    
            } else {
                $config.Office365.WizdomGraphUser.Password = $config.Office365.WizdomSearchUser.Password
                $WizdomGraphUser = $WizdomSearchUser
            }
        }
        if ($config.Office365.WizdomGraphUser.UserName -eq "") {$config.Office365.WizdomGraphUser.UserName = "Wizdom Graph User"}
        if (-not (($config.Office365.WizdomGraphUser.UsageLocation).ToUpper() -match '^(AF|AX|AL|DZ|AS|AD|AO|AI|AQ|AG|AR|AM|AW|AU|AT|AZ|BS|BH|BD|BB|BY|BE|BZ|BJ|BM|BT|BO|BQ|BA|BW|BV|BR|IO|BN|BG|BF|BI|KH|CM|CA|CV|KY|CF|TD|CL|CN|CX|CC|CO|KM|CG|CD|CK|CR|CI|HR|CU|CW|CY|CZ|DK|DJ|DM|DO|EC|EG|SV|GQ|ER|EE|ET|FK|FO|FJ|FI|FR|GF|PF|TF|GA|GM|GE|DE|GH|GI|GR|GL|GD|GP|GU|GT|GG|GN|GW|GY|HT|HM|VA|HN|HK|HU|IS|IN|ID|IR|IQ|IE|IM|IL|IT|JM|JP|JE|JO|KZ|KE|KI|KP|KR|KW|KG|LA|LV|LB|LS|LR|LY|LI|LT|LU|MO|MK|MG|MW|MY|MV|ML|MT|MH|MQ|MR|MU|YT|MX|FM|MD|MC|MN|ME|MS|MA|MZ|MM|NA|NR|NP|NL|NC|NZ|NI|NE|NG|NU|NF|MP|NO|OM|PK|PW|PS|PA|PG|PY|PE|PH|PN|PL|PT|PR|QA|RE|RO|RU|RW|BL|SH|KN|LC|MF|PM|VC|WS|SM|ST|SA|SN|RS|SC|SL|SG|SX|SK|SI|SB|SO|ZA|GS|SS|ES|LK|SD|SR|SJ|SZ|SE|CH|SY|TW|TJ|TZ|TH|TL|TG|TK|TO|TT|TN|TR|TM|TC|TV|UG|UA|AE|GB|US|UM|UY|UZ|VU|VE|VN|VG|VI|WF|EH|YE|ZM|ZW)$')) {
            $outputErrorCount++
            $outputConfigCheck += "$($outputErrorCount)) WizdomGraphUser Countrycode invalid`r`n"
        }

        if ($installMode -match "AppInsights") {
            if (-not ($config.Office365.ApplicationInsightsUser.Account -match '^[^\.][\w_\.-]*[^\.]@([\w]*\.){1,2}[\w]{2,3}$')) {
                $outputErrorCount++
                $outputConfigCheck += "$($outputErrorCount)) ApplicationInsightsUser missing or not formated as UPN in config file`r`n"
            } else {
                try {$wizdomApplicationInsightsUser = Get-AzureADUser -ObjectId $config.Office365.ApplicationInsightsUser.Account -ErrorAction SilentlyContinue} catch {}
                if ($wizdomApplicationInsightsUser -ne $null -and $removeMode -match "Office365Users") {
                    $output = Write-WizdomHost -messageType PROCESS -message "Removing Office365 user $($config.Office365.ApplicationInsightsUser.Account)" -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
                    if ($PSCmdlet.ShouldProcess($config.Office365.ApplicationInsightsUser.Account, "Remove User")) {
                        Remove-AzureAdUser -ObjectId $wizdomApplicationInsightsUser.UserPrincipalName | Write-Verbose
                    }
                    $wizdomApplicationInsightsUser = $null
                    Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
                }
                if ($wizdomApplicationInsightsUser -eq $null) {
                    if ($config.Office365.ApplicationInsightsUser.Password -eq "") {$config.Office365.ApplicationInsightsUser.Password = Get-RandomPassword}
                } elseif ($config.Office365.ApplicationInsightsUser.Password -eq "") {
                    $outputErrorCount++
                    $outputConfigCheck += "$($outputErrorCount)) $($config.Office365.ApplicationInsightsUser.Account) already exists, but no password has been supplied`r`n"
                } else {
                    try {
                        $credentialApplicationInsightsUser = New-Object System.Management.Automation.PSCredential($config.Office365.ApplicationInsightsUser.Account,(ConvertTo-SecureString -String $config.Office365.ApplicationInsightsUser.Password -AsPlainText -Force))
                        Connect-PnPOnline -ReturnConnection -Url $SharePointUrl -Credentials $credentialApplicationInsightsUser -ErrorAction Stop|write-verbose
                    } catch {
                        $outputErrorCount++
                        $outputConfigCheck += "$($outputErrorCount)) Wrong username or password for the Application Insights User account: $($config.Office365.ApplicationInsightsUser.Account)`r`n"
                    }
                }
            }
            if ($config.Office365.ApplicationInsightsUser.UserName -eq "") {$config.Office365.ApplicationInsightsUser.UserName = "Wizdom Analytics User"}
            if (-not (($config.Office365.ApplicationInsightsUser.UsageLocation).ToUpper() -match '^(AF|AX|AL|DZ|AS|AD|AO|AI|AQ|AG|AR|AM|AW|AU|AT|AZ|BS|BH|BD|BB|BY|BE|BZ|BJ|BM|BT|BO|BQ|BA|BW|BV|BR|IO|BN|BG|BF|BI|KH|CM|CA|CV|KY|CF|TD|CL|CN|CX|CC|CO|KM|CG|CD|CK|CR|CI|HR|CU|CW|CY|CZ|DK|DJ|DM|DO|EC|EG|SV|GQ|ER|EE|ET|FK|FO|FJ|FI|FR|GF|PF|TF|GA|GM|GE|DE|GH|GI|GR|GL|GD|GP|GU|GT|GG|GN|GW|GY|HT|HM|VA|HN|HK|HU|IS|IN|ID|IR|IQ|IE|IM|IL|IT|JM|JP|JE|JO|KZ|KE|KI|KP|KR|KW|KG|LA|LV|LB|LS|LR|LY|LI|LT|LU|MO|MK|MG|MW|MY|MV|ML|MT|MH|MQ|MR|MU|YT|MX|FM|MD|MC|MN|ME|MS|MA|MZ|MM|NA|NR|NP|NL|NC|NZ|NI|NE|NG|NU|NF|MP|NO|OM|PK|PW|PS|PA|PG|PY|PE|PH|PN|PL|PT|PR|QA|RE|RO|RU|RW|BL|SH|KN|LC|MF|PM|VC|WS|SM|ST|SA|SN|RS|SC|SL|SG|SX|SK|SI|SB|SO|ZA|GS|SS|ES|LK|SD|SR|SJ|SZ|SE|CH|SY|TW|TJ|TZ|TH|TL|TG|TK|TO|TT|TN|TR|TM|TC|TV|UG|UA|AE|GB|US|UM|UY|UZ|VU|VE|VN|VG|VI|WF|EH|YE|ZM|ZW)$')) {
                $outputErrorCount++
                $outputConfigCheck += "$($outputErrorCount)) ApplicationInsightsUser Countrycode invalid`r`n"
            }
        }

        #Azure Group
        if (-not ($config.Azure.SubscriptionId -match "^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$")) {
            $outputErrorCount++
            $outputConfigCheck += "$($outputErrorCount)) Azure SubscriptionID not a valid GUID`r`n"
        } else {
            if (-not (Set-AzureRmContext -SubscriptionId $config.Azure.SubscriptionId -ErrorAction SilentlyContinue -WhatIf:$false)) {
                $outputErrorCount++
                $outputConfigCheck += "$($outputErrorCount)) Azure SubscriptionId is not a valid Id for the logged in user`r`n"
            }

            #Add to output file
            $outFileArrayBasic += New-Object PSObject -Property @{
            Line = 15
            Field = "Azure SubscriptionID"
            Input = $config.Azure.SubscriptionId}

            try {
                $azureRoleAssignment = Get-AzureRmRoleAssignment -ExpandPrincipalGroups -SignInName $azureSubscriptionAdminUsr -ErrorAction SilentlyContinue
                if ($azureRoleAssignment -eq $null) {
                    $azureRoleAssignment = (Get-AzureRmRoleAssignment -IncludeClassicAdministrators).where({$_.SignInName -eq $azureSubscriptionAdminUsr})
                    if (-not (($azureRoleAssignment.RoleDefinitionName.ToLower().Contains("accountadministrator")) -or ($azureRoleAssignment.RoleDefinitionName.ToLower().Contains("serviceadministrator")) -or ($azureRoleAssignment.RoleDefinitionName.ToLower() -contains "contributor"))) {
                        $outputErrorCount++
                        $outputConfigCheck += "$($outputErrorCount)) $($azureSubscriptionAdminUsr) doesn't have sufficient (Service Administrator or Account Administrator (Admin or Co-Admin)) rights in the Azure subscription with ID $($config.Azure.SubscriptionId)`r`n"
                    }
                } else {
                    if (-not (($azureRoleAssignment.RoleDefinitionName.ToLower() -contains "owner") -or ($azureRoleAssignment.RoleDefinitionName.ToLower() -contains "contributor"))) {
                        $outputErrorCount++
                        $outputConfigCheck += "$($outputErrorCount)) $($azureSubscriptionAdminUsr) doesn't have sufficient (Owner/Contributor) rights in the Azure subscription with ID $($config.Azure.SubscriptionId)`r`n"
                    }
                }
            }
            catch {
                $testResourceGroupName = (New-Guid).ToString().Replace("-","")
                try {
                    $testResourceGroup = New-AzureRmResourceGroup -Name $testResourceGroupName -Location $config.$config.Azure.Location
                    if ($testResourceGroup -eq $null) {
                        $outputErrorCount++
                        $outputConfigCheck += "$($outputErrorCount)) $($azureSubscriptionAdminUsr) doesn't have sufficient (Owner) rights in the Azure subscription with ID $($config.Azure.SubscriptionId)`r`n"
                    } else {
                        Remove-AzureRmResourceGroup -id $testResourceGroup.ResourceId -Force -AsJob |Write-Verbose
                    }
                } catch {
                    $outputErrorCount++
                    $outputConfigCheck += "$($outputErrorCount)) $($azureSubscriptionAdminUsr) doesn't have sufficient (Owner) rights in the Azure subscription with ID $($config.Azure.SubscriptionId) or the Azure location is not valid`r`n"
                }
            }
        }

        if (-not ($config.Azure.companyname -match '^[A-Z][A-Z0-9-]{2,23}$')) {
            $outputErrorCount++
            $outputConfigCheck += "$($outputErrorCount)) Azure Companyname is not valid`r`n"
        } else {
            $websiteName = ($config.Wizdom.websiteName).replace('##companyname##',$config.Azure.companyname)
            $hostingPlanName = ($config.Wizdom.hostingPlanName).replace('##companyname##',$config.Azure.companyname).replace('##websitename##',$websiteName)
            $storageAccountName = ($config.Wizdom.storageAccountName).replace('##companyname##',$config.Azure.companyname).replace('##websitename##',$websiteName).tolower()
            $AppDatabaseName = ($config.Wizdom.AppDatabaseName).replace('##companyname##',$config.Azure.companyname).replace('##websitename##',$websiteName)
            $redisCacheName = ($config.Wizdom.redisCacheName).replace('##companyname##',$config.Azure.companyname).replace('##websitename##',$websiteName)
            $keyVaultName = ($config.Wizdom.keyVaultName).replace('##companyname##',$config.Azure.companyname).replace('##websitename##',$websiteName)
            $websiteUrl = "https://" + $websiteName.tolower() + ".azurewebsites.net"
            if ($installMode -match "AppInsights") {
                $applicationInsightsName = ($config.Wizdom.applicationInsightsName).replace('##companyname##',$config.Azure.companyname).replace('##websitename##',$websiteName)

                #Add to output file
                $outFileArrayAnalytics += New-Object PSObject -Property @{
                Line = 1
                Field = "Application Insights Instance"
                Input = $applicationInsightsName}

            }
            #Add to output file
            $outFileArrayBasic += New-Object PSObject -Property @{
            Line = 9
            Field = "CompanyName"
            Input = $config.Azure.companyname}
            $outFileArrayBasic += New-Object PSObject -Property @{
            Line = 17
            Field = "AppUrl"
            Input = $websiteUrl}

        }
        if ($config.Azure.Location -eq "") {
            $outputErrorCount++
            $outputConfigCheck += "$($outputErrorCount)) Azure Location is missing`r`n"
        } else {
            $resources = Get-AzureRmResourceProvider -ListAvailable
            if ($config.Azure.KeyVault.Location -eq "") {
                $config.Azure.KeyVault.Location = $config.Azure.Location
            }
            if ($installMode -match "AppInsights") {
                if ($resources.where({$_.ProviderNameSpace -ieq "microsoft.insights"}) -eq $null) {
                    if ($PSCmdlet.ShouldProcess("Microsoft.Insights", "Register Azure Resource Provider")) {
                        Register-AzureRmResourceProvider -ProviderNamespace Microsoft.Insights
                    }
                }
                if (-not $resources.where({$_.ProviderNameSpace -ieq "microsoft.insights"}).ResourceTypes.Where({$_.ResourceTypeName -ieq 'components' -and $_.Locations -ieq $config.Azure.ApplicationInsights.Location})) {
                    $outputErrorCount++
                    $outputConfigCheck += "$($outputErrorCount)) Azure Application Insights Location does not match a valid location that supports Application Insights`r`n"
                }
            }
            if (-not $resources.where({$_.ProviderNameSpace -ieq "microsoft.sql"}).ResourceTypes.Where({$_.ResourceTypeName -ieq 'servers' -and $_.Locations -ieq $config.Azure.Location})) {
                $outputErrorCount++
                $outputConfigCheck += "$($outputErrorCount)) Azure Location does not match a valid location that supports the SQL server service`r`n"
            }
            if (-not $resources.where({$_.ProviderNameSpace -ieq "microsoft.storage"}).ResourceTypes.Where({$_.ResourceTypeName -ieq 'storageaccounts/blobservices' -and $_.Locations -ieq $config.Azure.Location})) {
                $outputErrorCount++
                $outputConfigCheck += "$($outputErrorCount)) Azure Location does not match a valid location that supports the BLOB storage service`r`n"
            }
            if (-not $resources.where({$_.ProviderNameSpace -ieq "microsoft.web"}).ResourceTypes.Where({$_.ResourceTypeName -ieq 'sites' -and $_.Locations -ieq $config.Azure.Location})) {
                $outputErrorCount++
                $outputConfigCheck += "$($outputErrorCount)) Azure Location does not match a valid location that supports the Web Application service`r`n"
            }
            if ($config.Azure.KeyVault.enableKeyVault) {
                if (-not $resources.where({$_.ProviderNameSpace -ieq "microsoft.keyvault"}).ResourceTypes.Where({$_.ResourceTypeName -ieq 'vaults' -and $_.Locations -ieq $config.Azure.KeyVault.Location})) {
                    $outputErrorCount++
                    $outputConfigCheck += "$($outputErrorCount)) Azure Location does not match a valid location that supports the Azure Key Vault Service`r`n"
                }
            }
            if ($config.Azure.NumberOfUsers -ge 2001) {
                if (-not $resources.where({$_.ProviderNameSpace -ieq "microsoft.cache"}).ResourceTypes.Where({$_.ResourceTypeName -ieq 'redis' -and $_.Locations -ieq $config.Azure.Location})) {
                    $outputErrorCount++
                    $outputConfigCheck += "$($outputErrorCount)) Azure Location does not match a valid location that supports the Redis cache service`r`n"
                }
            }

            #Add to output file
            $outFileArrayBasic += New-Object PSObject -Property @{
            Line = 21
            Field = "Azure Region Location"
            Input = $config.Azure.Location}
             
        }
        if ($config.Azure.ResourceGroupName -match "^[A-Z0-9_()-\.]{1,90}(?<!\.)$") {

            #Add to output file
            $outFileArrayBasic += New-Object PSObject -Property @{
            Line = 20
            Field = "RessourceGroup Azure"
            Input = $config.Azure.ResourceGroupName}


            try {
                $azureResourceGroup = Get-AzureRmResourceGroup -Name ($config.Azure.ResourceGroupName) -ErrorAction SilentlyContinue
                if ($azureResourceGroup -ne $null) {
                    if ($removeMode -match "AzureResourceGroup") {
                        $output = Write-WizdomHost -messageType PROCESS -message ("Removing Azure Resource Group " + $config.Azure.ResourceGroupName) -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
                        try {
                            if ($PSCmdlet.ShouldProcess($azureResourceGroup.ResourceGroupName,"Remove Azure Resource Group")) {
                                if ($ConfirmRemove) {
                                    Remove-AzureRmResourceGroup -Id $azureResourceGroup.ResourceId -Force |write-verbose
                                } else {
                                    Remove-AzureRmResourceGroup -Id $azureResourceGroup.ResourceId -Force -Confirm:$false |write-verbose
                                }
                            }
                            Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
                        } catch {
                            Write-WizdomHost -messageType ERROR -outputMaxLength $outputMaxLength -initialStringLength $output -afterOutputMessage $_.Exception.Message  -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
                        }
                        $output = Write-WizdomHost -messageType PROCESS -message ("Waiting for Azure DB name to become available again") -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
                        $dotCount = 0
                        if ($PSCmdlet.ShouldProcess($config.Azure.Database.ServerName, "waiting for Azure DB and blob to be deleted on Azure")) {
                            do {
                                try {
                                    Invoke-WebRequest -Method get ($config.Azure.Database.ServerName+".database.windows.net") -TimeoutSec 1 -UseBasicParsing
                                    Start-Sleep -Seconds $waitLong
                                    if (($output + $dotCount) -le ($outputMaxLength - 2)) {
                                        Write-Host -NoNewline "." -ForegroundColor White
                                        $dotCount++
                                    }

                                } catch {
                                    if ($_.Exception.message -eq "The operation has timed out.") {
                                        $sqlServerNameAvailable = $false
                                    } else {
                                        $sqlServerNameAvailable = $true
                                    }
                                }

                            } until ($sqlServerNameAvailable)
                            do {
                                try {
                                    Invoke-WebRequest -Method get ($storageAccountName + ".blob.core.windows.net") -TimeoutSec 1 -UseBasicParsing
                                    Start-Sleep -Seconds $waitLong
                                    if (($output + $dotCount) -le ($outputMaxLength - 2)) {
                                        Write-Host -NoNewline "." -ForegroundColor White
                                        $dotCount++
                                    }

                                } catch {
                                    if ($_.Exception.message -eq "The operation has timed out.") {
                                        $blobServerNameAvailable = $false
                                    } else {
                                        $blobServerNameAvailable = $true
                                    }
                                }

                            } until ($blobServerNameAvailable)
                        }
                        Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength ($output+$dotCount) -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
                    } else {
                        $azureResources = (Get-AzureRmResource -ResourceGroupName $config.Azure.ResourceGroupName) #.where({$_.Name -match $config.Azure.companyname.ToLower()})
                        $azureServices = @()
                        
                        $azureServices += $azureResources.where({$_.Name -eq $config.Azure.Database.ServerName}) 
                        $azureServices += $azureResources.where({$_.Name -eq $websitename}) 
                        $azureServices += $azureResources.where({$_.Name -eq $hostingPlanName}) 
                        $azureServices += $azureResources.where({$_.Name -eq $storageAccountName}) 
                        if ($config.Azure.Cache -eq "Redis") {
                            $azureServices += $azureResources.where({$_.Name -eq $redisCacheName}) 
                        }
                        if ($installMode -match "AppInsights") {
                            $azureServices += $azureResources.where({$_.Name -eq $applicationInsightsName }) 
                        }
                        if ($config.Azure.KeyVault.enableKeyVault -and ($null -ne $azureResources.where({$_.Name -eq $keyVaultName}))) {
                            $azureServices += $azureResources.where({$_.Name -eq $keyVaultName})
                        }

                        foreach ($azureService in $azureServices) {
                            if ($null -ne $azureService) {
                                if ($removeMode -match "AzureServices") {
                                    $output = Write-WizdomHost -messageType PROCESS -message "Removing Azure Resource $($azureService.Name)" -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
                                    try {
                                        if ($PSCmdlet.ShouldProcess($azureService.Name, "Remove Azure Resource")) {
                                            if ($ConfirmRemove) {
                                                Remove-AzureRmResource -ResourceId $azureService.ResourceId -Force |write-verbose
                                            } else {
                                                Remove-AzureRmResource -ResourceId $azureService.ResourceId -Force -Confirm:$false |write-verbose
                                            }
                                        }
                                        Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
                                    } catch {
                                        Write-WizdomHost -messageType ERROR -outputMaxLength $outputMaxLength -initialStringLength $output -afterOutputMessage $_.Exception.Message  -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
                                    }
                                }
                                else {
                                    $outputErrorCount++
                                    $outputConfigCheck += "$($outputErrorCount)) Azure Resource Service $($azureService.Name) already exists in Resource Group $($config.Azure.ResourceGroupName)`r`n"}
                            }
                        }
                    }
                }            
            } catch {}
        } else {
            $outputErrorCount++
            $outputConfigCheck += "$($outputErrorCount)) Azure Resource Group Name is missing or incorrect`r`n"
        }
        if (-not ($config.Azure.NumberOfUsers.GetType().Name -eq "Int32")) {
            $outputErrorCount++
            $outputConfigCheck += "$($outputErrorCount)) Azure Number of Users needs to be set to a number`r`n"
        }
        if (-not ($config.Azure.Database.UserName -match '^[A-Z]*$')) {
            $outputErrorCount++
            $outputConfigCheck += "$($outputErrorCount)) Azure Database username is missing or are containing other characters than a-z`r`n"
        }
        
        #Add to output file
        $outFileArrayBasic += New-Object PSObject -Property @{
        Line = 10
        Field = "DBusername"
        Input = $config.Azure.Database.UserName}

        if ($config.Azure.Database.UserPassword -eq "") {$config.Azure.Database.UserPassword = Get-RandomPassword}
        
        #Add to output file
        $outFileArrayBasic += New-Object PSObject -Property @{
        Line = 11
        Field = "DBuserpassword"
        Input = $config.Azure.Database.UserPassword}

        
        $DBPassword = ConvertTo-SecureString -String $config.Azure.Database.UserPassword -AsPlainText -Force
        if (-not ($config.Azure.Database.ServerName -cmatch "^(?![\.-])[a-z0-9\.-]{1,63}(?<![\.-])$")) {
            $outputErrorCount++
            $outputConfigCheck += "$($outputErrorCount)) Azure Database Servername $($config.Azure.Database.ServerName) is not valid. Change Config file Azure.Database.ServerName`r`n"
        } else {
            $config.Azure.Database.ServerName = $config.Azure.Database.ServerName.ToLower()
            
            #Add to output file
            $outFileArrayBasic += New-Object PSObject -Property @{
            Line = 12
            Field = "DBservername"
            Input = $config.Azure.Database.ServerName}
            
            try {Invoke-WebRequest -Method get ($config.Azure.Database.ServerName+".database.windows.net") -TimeoutSec 1 -UseBasicParsing} catch {if ($_.Exception.message -eq "The operation has timed out.") {
                $outputErrorCount++
                $outputConfigCheck += "$($outputErrorCount)) The Azure sql server name is already in use. It needs to be globally unique.`r`n"
            }}
        }

        if ((Get-AzureRmStorageAccountNameAvailability -Name $storageAccountName).Reason -eq "AlreadyExists") {
                $outputErrorCount++
                $outputConfigCheck += "$($outputErrorCount)) The Azure Storage Account name is already in use. It needs to be globally unique.`r`n"
        }

        #AzureAD Group
        if ($config.AzureAD.WizdomApplicationName -ne "") {
            try {
                $azureADApp = (Get-AzureADApplication -All:$true).Where({$_.DisplayName -eq $config.AzureAD.WizdomApplicationName})
                if ($azureADApp -ne $null) {
                    if (($removeMode -ne $null) -and ($config.AzureAD.AppId -eq "")) {
                        try {
                            $output = Write-WizdomHost -messageType PROCESS -message ("Removing Azure AD App " + $config.AzureAD.WizdomApplicationName)
                            if ($PSCmdlet.ShouldProcess($azureADApp.DisplayName, "Remove AD Application")) {
                                Remove-AzureADApplication -ObjectId $azureADApp.ObjectId |write-verbose
                            }
                            Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
                        } catch {
                            Write-WizdomHost -messageType ERROR -outputMaxLength $outputMaxLength -initialStringLength $output -afterOutputMessage $_.Exception.Message -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
                        }
                    } elseif ($config.AzureAD.AppId -ne $null -and $config.AzureAD.AppId -ne "" -and $config.AzureAD.Secret -ne $null -and $config.AzureAD.Secret -ne "") {
                    } else {
                        $outputErrorCount++
                        $outputConfigCheck += "$($outputErrorCount)) Wizdom AD Application is already present - either include the -removeMode switch or supply the existing ClientId/Secret under the AzureAD section of the JSON config file`r`n"
                    }
                }
            } catch {}
            finally {
            }
        } else {
            $outputErrorCount++
            $outputConfigCheck += "$($outputErrorCount)) Wizdom AD Application name is missing`r`n"
        }

            #Wizdom Group
            if (-not ($config.Wizdom.LicenseID -match "^[0-9]{1,4}$|^$")) {
            $outputErrorCount++
            $outputConfigCheck += "$($outputErrorCount)) Licence ID is incorrect`r`n"
            }
            $installFiles = @()
            $installFiles += $PSScriptRoot + $config.Wizdom.applicationsInsightTemplate
            $installFiles += $PSScriptRoot + $config.Wizdom.memoryCacheTemplate
            $installFiles += $PSScriptRoot + $config.Wizdom.redisCacheTemplate
            $installFiles += $PSScriptRoot + $config.Wizdom.notificationHubTemplate
            $installFiles += $PSScriptRoot + $config.Wizdom.mobileModuleZip
            $installFiles += $PSScriptRoot + $config.Wizdom.timerJobZip
            $installFiles += $PSScriptRoot + $config.Wizdom.appTrustPuppeteerScript
            $installFiles += $PSScriptRoot + $config.Wizdom.appTrustPuppeteerScriptMFA
            $installFiles += $PSScriptRoot + $config.Wizdom.licensePairPuppeteerScript
            $installFiles += $PSScriptRoot + $config.Wizdom.lightningPageTemplate
            $installFiles += $PSScriptRoot + $config.Wizdom.searchConfigTemplate
            $installFiles += $PSScriptRoot + $config.Wizdom.nodejsWorkingDirectory

            foreach ($installFile in $installFiles) {
            if (-not (Test-Path $installFile)) {
                $outputErrorCount++
                $outputConfigCheck += "$($outputErrorCount)) File or Directory $($installFile) is missing. Please add installation files or change the path in the config file 'Wizdom' section`r`n"
            }
            }

#region ConfigCheck and uninstall Mobile App
    $websiteUrlMobile = $websiteUrl.Replace($websiteName.tolower(),($websiteName + $config.MobileApp.Azure.WebAppSuffix).tolower())

    if (($installMode -eq "NoneTestConfig" -or $removeMode -match "AzureResources" -or $removeMode -match "SharePointSites") -and ($config.MobileApp.SharePoint.Url -ne "") -and ($config.MobileApp.SharePoint.Url -ne $null)) {
        $mobileParameters = @{
            mobileModuleName = $config.MobileApp.Wizdom.ModuleName
            wizdomMobileSiteSPO = ($SharePointUrl + "/sites/" + $config.MobileApp.SharePoint.Url)
            wizdomWebsiteUrl = $websiteUrl
            sharePointAdminUsr = $SharePointTenantAdmin
            mobileSiteCollectionOwner = $config.MobileApp.SharePoint.Owner
            mobileSiteCollectionTitle = $config.MobileApp.SharePoint.Title
            mobileSiteCollectionLocaleId = $config.MobileApp.SharePoint.LocaleId
            azureResourceGroupName = $config.Azure.ResourceGroupName
            azureMobileWebAppUrl = $websiteUrlMobile
            mobileModuleZip = $config.Wizdom.mobileModuleZip
            outputMaxLength = $outputMaxLength
            lightningPageTemplate = $config.Wizdom.lightningPageTemplate
            installMode = "NoneTestConfig"
            removeMode = $removeMode
            wizdomSharePointAppName = $config.SharePointClassic.WizdomApp.Title
            enableNotifications = $config.MobileApp.Azure.enableNotifications
            notificationHubNamespace = $config.Wizdom.notificationHubNamespace
            notificationHubName = $config.Wizdom.notificationHubName
            notificationHubTemplate = $config.Wizdom.notificationHubTemplate
            ConfirmRemove = $ConfirmRemove
        }
        Install-WizdomMobile @mobileParameters -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    }
#endregion

  
    If ($outputConfigCheck -ne "") { 
        if ($null -eq $removeMode) {Write-Host ("." * [math]::max(($outputMaxLength - $output - 3),0)) -ForegroundColor White -NoNewline}
        Write-Host "ERROR" -ForegroundColor Red

        $oldSharepointApps = Get-MsolServicePrincipal | Where DisplayName -eq $config.SharePointClassic.WizdomApp.Name
        if ($oldSharepointApps -ne $null) {
            try {
            $output = Write-WizdomHost -messageType PROCESS -message ("Removing SharePoint App from App Catalog") -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
                if ($PSCmdlet.ShouldProcess($config.SharePointClassic.WizdomApp.Title,"Remove App")) {
                    foreach ($oldSharepointApp in $oldSharepointApps) {
                        Remove-MsolServicePrincipal -ObjectId $oldSharepointApp.ObjectId -ErrorAction SilentlyContinue|Write-Verbose
                    }
                }
            Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
            } catch {
                Write-WizdomHost -messageType ERROR -outputMaxLength $outputMaxLength -initialStringLength $output -afterOutputMessage $_.Exception.Message 
            }
        }

        Write-Host "Error in config file. Exiting Script:" -ForegroundColor Red
        Write-Host $outputConfigCheck -ForegroundColor White
        if ($PSCmdlet.ShouldProcess("Powershell Script", "Exit")) {
            Exit
        }
    } else {
        if ($null -eq $removeMode) {Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
        } else {
            Write-Host "Config verification complete, installation removed." -ForegroundColor DarkGray
        }
    }
#endregion

#region Checking if Application Catalog exists
    $output = Write-WizdomHost -messageType PROCESS -message "Checking if Application Catalog exists" -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
            $appCatalogUrl = Get-PnPTenantAppCatalogUrl -Connection $pnpSharePointAdminAppContext
        if ($appCatalogUrl -eq "" -or $appCatalogUrl -eq $null) {
            Write-WizdomHost -messageType ERROR -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference

            Write-Host "App Catalog is missing. Stopping installation script" -ForegroundColor red
            Write-Host "Log in to $SharePointAdminUrl with $SPOUser" -ForegroundColor White
            Write-Host "Select apps in the left pane, and then select App Catalog" -ForegroundColor White
            Write-Host "If the App Catalog site doesn't open, select Create a new app catalog site, and then select OK" -ForegroundColor White
            Write-Host "On the Create App Catalog Site Collection page, enter the required information, and then select OK" -ForegroundColor White
            Write-Host "Title = App Catalog" -ForegroundColor White
            Write-Host "Site address = /sites/appcatalog" -ForegroundColor White
            Write-Host "Time zone = <customer defined time zone>" -ForegroundColor White
            Write-Host "Language = <customer defined language>" -ForegroundColor White
            Write-Host "Administrator = <customer defined owner" -ForegroundColor White
            Exit
        } else {
            Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
        }
#endregion

#region Checking Application Catalog rights
    $output = Write-WizdomHost -messageType PROCESS -message "Checking Application Catalog rights" -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
        $appCatalogAdmin = $null
        $appCatalogAdminGroup = $null
        do {    
            Start-Sleep -Seconds $waitShort
            try {
                $appCatalogAdmin = Get-SPOUser -site $appCatalogUrl -LoginName $SharePointTenantAdmin
                $appCatalogAdminGroup = get-spositegroup -Site $appCatalogUrl
            } catch {
                $appCatalogAdmin = $null
                $appCatalogAdminGroup = $null
            }
        } while ($null -eq $appCatalogAdmin -and $null -eq $appCatalogAdminGroup)
        if ((($appCatalogAdmin).where({$_.IsSiteAdmin -eq $true}).count -eq 0) -and (($appCatalogAdminGroup.where{($_.roles -match "Full Control" -or $_.roles -match "Contribute") -and $_.users -match $SharePointTenantAdmin}).count -eq 0)) {
            Write-WizdomHost -messageType ERROR -outputMaxLength $outputMaxLength -initialStringLength $output -afterOutputMessage "$($SharePointTenantAdmin) doesn't have access to the App Catalog at $($appCatalogUrl) as either Owner or Site Collection Owner. Stopping installation script" -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
            Exit
        }
        #if (((Get-SPOUser -site $appCatalogUrl).where({$_.loginname -eq $SharePointTenantAdmin -and $_.IsSiteAdmin -eq $true}).count -eq 0) -and (((get-spositegroup -Site $appCatalogUrl).where{($_.roles -match "Full Control" -or $_.roles -match "Contribute") -and $_.users -match $SharePointTenantAdmin}).count -eq 0)) {
    Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    if ($installMode -eq "NoneTestConfig") {
        $oldSharepointApps = Get-MsolServicePrincipal | Where DisplayName -eq $config.SharePointClassic.WizdomApp.Name
        if ($oldSharepointApps -ne $null) {
            try {
            $output = Write-WizdomHost -messageType PROCESS -message ("Removing SharePoint App from App Catalog") -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
                if ($PSCmdlet.ShouldProcess($config.SharePointClassic.WizdomApp.Title,"Remove App")) {
                    foreach ($oldSharepointApp in $oldSharepointApps) {
                        Remove-MsolServicePrincipal -ObjectId $oldSharepointApp.ObjectId -ErrorAction SilentlyContinue|Write-Verbose
                    }
                }
            Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
            } catch {
                Write-WizdomHost -messageType ERROR -outputMaxLength $outputMaxLength -initialStringLength $output -afterOutputMessage $_.Exception.Message 
            }
        }

		Write-Host "Running in 'NoneTestConfig' mode. Exiting script."
        Write-Host "Everything checks out ok." -ForegroundColor Green																	   
        Exit
    }
#endregion 

#region Checking for Global Wizdom Termset and adding it if needed
    $output = Write-WizdomHost -messageType PROCESS -message ("Checking for Global Wizdom Termset and adding it if needed") -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    try {
        if ($installmode -ne "NoneTestConfig") {
            $WizdomTermStoreGlobalGroupName = "Wizdom Global"
            $WizdomTermStoreGlobalGroupId = "10000001-1001-1001-1001-100000000001"
            $WizdomTermStoreTermSetNames = @("Wizdom_Languages", "Wizdom_RelatedTopic", "Wizdom_ManualType", "Wizdom_ManualArea")
            $wizdomTermSetGroup = Get-PnPTermGroup -Identity $WizdomTermStoreGlobalGroupId -Includes TermSets -Connection $pnpSharePointAdminContext -ErrorAction SilentlyContinue
            if ($wizdomTermSetGroup -eq $null) {
                New-PnPTermGroup -Name $WizdomTermStoreGlobalGroupName -Id $WizdomTermStoreGlobalGroupId -Connection $pnpSharePointAdminContext | Write-Verbose
                $wizdomTermSetGroup = Get-PnPTermGroup -Identity $WizdomTermStoreGlobalGroupId -Includes TermSets -Connection $pnpSharePointAdminContext -ErrorAction SilentlyContinue
            }
            Foreach($wizdomTermSetName in $WizdomTermStoreTermSetNames){
                if ($wizdomTermSetGroup.TermSets.Where({$_.Name -eq $wizdomTermSetName}) -eq $null -or $wizdomTermSetGroup.TermSets.Where({$_.Name -eq $wizdomTermSetName}).Count -eq 0) {
                    #Fixing issue in PnP https://github.com/SharePoint/PnP-PowerShell/wiki/New-PnPTerm---Specified-argument-was-out-of-the-range-of-valid-values
                    try {
                        New-PnPTermSet -Name $wizdomTermSetName -TermGroup $WizdomTermStoreGlobalGroupName -Connection $pnpSharePointAdminContext -ErrorAction SilentlyContinue| Write-Verbose
                    } catch {
                        New-PnPTermSet -Name $wizdomTermSetName -TermGroup $WizdomTermStoreGlobalGroupName -Connection $pnpSharePointAdminContext -Lcid $config.SharePointClassic.IntranetSiteCollection.LocaleId| Write-Verbose
                    }
                }
            }
        }
        Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    } catch {
        Write-WizdomHost -messageType ERROR -outputMaxLength $outputMaxLength -initialStringLength $output -afterOutputMessage $_.Exception.Message 
        Write-Host "User: $($pnpSharePointAdminContext.PSCredential.UserName)" -ForegroundColor Red
        Write-Host "Url: $($pnpSharePointAdminContext.Url)" -ForegroundColor Red
    }
#endregion

#region Processing the search user
    $output = Write-WizdomHost -messageType PROCESS -message ("Processing the search user '" + $config.Office365.WizdomSearchUser.Account + "'") -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    if ($null -eq $wizdomSearchUser) { 
        if ($PSCmdlet.ShouldProcess($config.Office365.WizdomSearchUser.Account,"Create User")) {
            $output2 = (" - Creating user, assign an Office 365 F1 License as a minimum")
            Write-Host $output2 -ForegroundColor Green -NoNewline
            $tenantAdminPasswordProfile.Password = $config.Office365.WizdomSearchUser.Password
            $wizdomSearchUser = New-AzureADUser -DisplayName $config.Office365.WizdomSearchUser.UserName -GivenName $config.Office365.WizdomSearchUser.UserName.Split(" ",2)[0] -Surname $config.Office365.WizdomSearchUser.UserName.Split(" ",2)[1] -UserPrincipalName $config.Office365.WizdomSearchUser.Account -UsageLocation $config.Office365.WizdomSearchUser.UsageLocation -MailNickName $config.Office365.WizdomSearchUser.UserName.Replace(' ','') -AccountEnabled $true -PasswordProfile $tenantAdminPasswordProfile -PasswordPolicies "DisablePasswordExpiration"
        }
        Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength ($output+$output2.Length) -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    } else {
        Write-WizdomHost -messageType WARNING -outputMaxLength $outputMaxLength -initialStringLength $output -message " - User already exists" -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    }
#endregion

#region Processing the graph user
    $output = Write-WizdomHost -messageType PROCESS -message ("Processing the graph user '" + $config.Office365.WizdomGraphUser.Account + "'") -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    if ($config.Office365.WizdomGraphUser.Account -eq $config.Office365.WizdomSearchUser.Account) {
        $WizdomGraphUser = $WizdomSearchUser
    } 

    if ($null -eq $WizdomGraphUser) { 
        if ($PSCmdlet.ShouldProcess($config.Office365.WizdomGraphUser.Account,"Create User")) {
            $output2 = (" - Creating user, assign an Office 365 F1 License as a minimum if Teams will be used")
            Write-Host $output2 -ForegroundColor Green -NoNewline
            $tenantAdminPasswordProfile.Password = $config.Office365.WizdomGraphUser.Password
            $wizdomADGraphUser = New-AzureADUser -DisplayName $config.Office365.WizdomGraphUser.UserName -GivenName $config.Office365.WizdomGraphUser.UserName.Split(" ",2)[0] -Surname $config.Office365.WizdomGraphUser.UserName.Split(" ",2)[1] -UserPrincipalName $config.Office365.WizdomGraphUser.Account -UsageLocation $config.Office365.WizdomGraphUser.UsageLocation -MailNickName $config.Office365.WizdomGraphUser.UserName.Replace(' ','') -AccountEnabled $true -PasswordProfile $tenantAdminPasswordProfile -PasswordPolicies "DisablePasswordExpiration"
        }
        Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength ($output+$output2.Length) -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    } else {
        $wizdomADGraphUser = get-azureaduser -objectid $config.Office365.WizdomGraphUser.Account -ErrorAction SilentlyContinue
        $teamsLicenses = (((Get-AzureADSubscribedSku).where({$_.ServicePlans.ServicePlanName -eq "TEAMS1"})).ObjectID).replace((Get-AzureADTenantDetail).ObjectId+"_","")
        if ($wizdomADGraphUser.AssignedLicenses.SkuId -in $teamsLicenses) {
            Write-WizdomHost -messageType WARNING -outputMaxLength $outputMaxLength -initialStringLength $output -message " - User already exists" -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
        } else {
            Write-WizdomHost -messageType WARNING -outputMaxLength $outputMaxLength -initialStringLength $output -message " - User already exists, assign an Office 365 F1 License as a minimum if Teams will be used" -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
        }
    
    }

    $output = Write-WizdomHost -messageType PROCESS -message ("Granting Teams Service Administrator Rights to the Graph User") -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    try {
        #Add Graph User to the Teams Administrator Role
        $teamsAdminRole = (Get-AzureADDirectoryRole).where({$_.DisplayName -eq "Teams Service Administrator"})
        if ($teamsAdminRole.Count -eq 0) {
            $teamsAdminRoleTemplate = Get-AzureADDirectoryRoleTemplate | Where-Object {$_.displayName -eq "Teams Service Administrator"}
            Enable-AzureADDirectoryRole -RoleTemplateId $teamsAdminRoleTemplate.ObjectId | Write-Verbose

            # Fetch User Account Administrator role instance again
            $teamsAdminRole = (Get-AzureADDirectoryRole).where({$_.DisplayName -eq "Teams Service Administrator"})
        }
        if ((Get-AzureADDirectoryRoleMember -objectid $teamsAdminRole.ObjectId).where({$_.ObjectId -eq $wizdomADGraphUser.ObjectId}) -eq $null) {
            Add-AzureADDirectoryRoleMember -ObjectId $teamsAdminRole.ObjectId -RefObjectId $wizdomADGraphUser.ObjectId | write-verbose
        }
        Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength ($output) -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    } catch {
        Write-WizdomHost -messageType ERROR -outputMaxLength $outputMaxLength -initialStringLength $output -message " - Failed. Most likely insufficient privileges to enable role" 
    }
#endregion

#region Processing the application insights user
    if ($installMode -match "AppInsights") {
        $output = Write-WizdomHost -messageType PROCESS -message ("Processing the application insights user '" + $config.Office365.ApplicationInsightsUser.UserName + "'") -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
        if ($null -eq $wizdomApplicationInsightsUser) { 
            if ($PSCmdlet.ShouldProcess($config.Office365.ApplicationInsightsUser.UserName,"Create User")) {
                $output2 = (" - Creating user")
                Write-Host $output2 -ForegroundColor Green -NoNewline
                $tenantAdminPasswordProfile.Password = $config.Office365.ApplicationInsightsUser.Password
                $wizdomSearchUser = New-AzureADUser -DisplayName $config.Office365.ApplicationInsightsUser.UserName -GivenName $config.Office365.ApplicationInsightsUser.UserName.Split(" ",2)[0] -Surname $config.Office365.ApplicationInsightsUser.UserName.Split(" ",2)[1] -UserPrincipalName $config.Office365.ApplicationInsightsUser.Account -UsageLocation $config.Office365.ApplicationInsightsUser.UsageLocation -MailNickName $config.Office365.ApplicationInsightsUser.UserName.Replace(' ','') -AccountEnabled $true -PasswordProfile $tenantAdminPasswordProfile -PasswordPolicies "DisablePasswordExpiration"
            }
            Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength ($output+$output2.Length) -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
        } else {
            Write-WizdomHost -messageType WARNING -outputMaxLength $outputMaxLength -initialStringLength $output -message " - User already exists" -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
        }
    }
#endregion

#region Creating Wizdom site
    try {
        if ($environment -eq 'Classic' -or $environment -eq 'Both') {
            $output = Write-WizdomHost -messageType PROCESS -message ("Creating Wizdom site '" + $wizdomSiteSPO + "'") -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
            #get group
            if ((get-sposite -Limit ALL| Where Url -eq $wizdomSiteSPO) -eq $null ) {
                $dotCount = 0
                if (($config.Wizdom.siteCollectionBaseTemplate).tolower() -eq "publishing") {
                    $siteTemplate = ((Get-SPOWebTemplate).where({$_.Title -like "*Publishing*" -and $_.LocaleId -eq $config.SharePointClassic.IntranetSiteCollection.LocaleId})).Name
                } else {
                    $siteTemplate = "STS#0"
                }
                if (-not $config.SharePointClassic.IntranetSiteCollection.InstallOnRootSite) {
                    $retryCount = 0
                    do {
                        try {
                            try {
                                $deletedSite = Get-SPODeletedSite -Identity $wizdomSiteSPO -ErrorAction Stop
                                if ($deletedSite -ne $null) {Remove-SPODeletedSite -Identity $wizdomSiteSPO -Confirm:$false}
                            } catch {}
                            if ($PSCmdlet.ShouldProcess($config.SharePointClassic.IntranetSiteCollection.Title, "Create Site")) {
                                New-SPOSite -Url $wizdomSiteSPO -Owner $SharePointTenantAdmin -StorageQuota 1024 -Title $config.SharePointClassic.IntranetSiteCollection.Title -CompatibilityLevel 15 -LocaleId $config.SharePointClassic.IntranetSiteCollection.LocaleId -Template $siteTemplate
                            }
                            $siteCreated = $true
                        } catch {
                            $siteCreated = $false
                            if ($retryCount -eq 0) {
                                Write-host $_
                                $output = Write-WizdomHost -messageType PROCESS -message "SharePoint has put deletion of previous site in queue. It might take 10-15 mins for it to become available again" -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
                            } else {
                                if (($output + $dotCount) -le ($outputMaxLength - 2)) {
                                    Write-Host -NoNewline "." -ForegroundColor White
                                    $dotCount++
                                }
                            }
                            $retryCount++
                            Start-Sleep -Seconds $waitVeryLong
                        }
                    } until ($siteCreated)
                } else {
                    if ($PSCmdlet.ShouldProcess($wizdomSiteSPO, "Set Deny Add and Customize Pages to False")) {
                        Set-SPOSite $wizdomSiteSPO -DenyAddAndCustomizePages 0
                    }
                }
                $pnpWizdomSiteAppContext = Connect-PnPOnline -ReturnConnection -Url $wizdomSiteSPO  -AppId $clientId -AppSecret $clientSecret
                if ($PSCmdlet.ShouldProcess($config.SharePointClassic.IntranetSiteCollection.Owner, "Add User as Site Collection Owner")) {
                    do {
                        Start-Sleep -Seconds $waitShort
                        $ownerGroup = Get-PnPGroup -Connection $pnpWizdomSiteAppContext -AssociatedOwnerGroup -ErrorAction SilentlyContinue
                    } until ($ownerGroup -ne $null)
                    do {
                        try {
                            Add-SPOUser -site $wizdomSiteSPO -LoginName $config.SharePointClassic.IntranetSiteCollection.Owner -group $ownerGroup.title |write-verbose
                            do {    
                                Start-Sleep -Seconds $waitShort
                                try {
                                    $sharepointSiteOwner = Get-SPOUser -site $wizdomSiteSPO -LoginName $config.SharePointClassic.IntranetSiteCollection.Owner -ErrorAction SilentlyContinue
                                } catch {
                                    $sharepointSiteOwner = $null
                                }
                            } while ($null -eq $sharepointSiteOwner)
					        Set-SPOUser -site $wizdomSiteSPO -LoginName $config.SharePointClassic.IntranetSiteCollection.Owner -IsSiteCollectionAdmin $true |write-verbose
                        } catch {
                            Start-Sleep -Seconds $waitShort
                        }
                    } until ($sharepointSiteOwner -ne $null)
                }
                if ($PSCmdlet.ShouldProcess($config.Office365.WizdomSearchUser.Account, "Add User to Visitors Group")) {
                    do {
                        Start-Sleep -Seconds $waitShort
                        $visitorsGroup = Get-PnPGroup -Connection $pnpWizdomSiteAppContext -AssociatedVisitorGroup -ErrorAction SilentlyContinue
                    } until ($visitorsGroup -ne $null)
                    do {
                        try {                    
                            Add-SPOUser -site $wizdomSiteSPO -LoginName $config.Office365.WizdomSearchUser.Account -group $visitorsGroup.title |write-verbose
                            do {
                                Start-Sleep -Seconds $waitShort
                                try {
                                    $sharepointSiteSearchUser = Get-SPOUser -site $wizdomSiteSPO -LoginName $config.Office365.WizdomSearchUser.Account -ErrorAction SilentlyContinue
                                } catch {
                                    $sharepointSiteSearchUser = $null
                                }
                            } while ($null -eq $sharepointSiteSearchUser)
                        } catch {
                            Start-Sleep -Seconds $waitShort
                        }
                    } until ($sharepointSiteSearchUser -ne $null)
                }
                Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength ($output+$dotCount) -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
            } else {
                $pnpWizdomSiteAppContext = Connect-PnPOnline -ReturnConnection -Url $wizdomSiteSPO  -AppId $clientId -AppSecret $clientSecret
                do {
                    Start-Sleep -Seconds $waitShort
                    $visitorsGroup = Get-PnPGroup -Connection $pnpWizdomSiteAppContext -AssociatedVisitorGroup -ErrorAction SilentlyContinue
                } until ($visitorsGroup -ne $null)
                if (@(Get-PnPGroupMembers -Identity $visitorsGroup -Connection $pnpWizdomSiteAppContext).where({$_.LoginName -eq ("i:0#.f|membership|" + $config.Office365.WizdomSearchUser.Account)})[0].id -eq $null) {
                    if ($PSCmdlet.ShouldProcess($config.Office365.WizdomSearchUser.Account, "Add User to Visitors Group")) {
                        do {
                            try {
                                Add-SPOUser -site $wizdomSiteSPO -LoginName $config.Office365.WizdomSearchUser.Account -group $visitorsGroup.title |write-verbose
                                do {
                                    Start-Sleep -Seconds $waitShort
                                    try {
                                        $sharepointSiteSearchUser = Get-SPOUser -site $wizdomSiteSPO -LoginName $config.Office365.WizdomSearchUser.Account -ErrorAction SilentlyContinue
                                    }
                                    catch {
                                        $sharepointSiteSearchUser = $null
                                    }
                                } while ($null -eq $sharepointSiteSearchUser)
                            } catch {
                                Start-Sleep -Seconds $waitShort
                            }
                        } until ($sharepointSiteSearchUser -ne $null)
                    }
                }
                do {
                    Start-Sleep -Seconds $waitShort
                    $ownerGroup = Get-PnPGroup -Connection $pnpWizdomSiteAppContext -AssociatedOwnerGroup -ErrorAction SilentlyContinue
                } until ($ownerGroup -ne $null)
                if (@(Get-PnPGroupMembers -Identity $ownerGroup -Connection $pnpWizdomSiteAppContext).where({$_.LoginName -eq ("i:0#.f|membership|" + $config.SharePointClassic.IntranetSiteCollection.Owner)})[0].id -eq $null) {
                    if ($PSCmdlet.ShouldProcess($config.SharePointClassic.IntranetSiteCollection.Owner, "Add User as Site Collection Owner")) {
                        do {
                            try {
                                Add-SPOUser -site $wizdomSiteSPO -LoginName $config.SharePointClassic.IntranetSiteCollection.Owner -group $ownerGroup.title |write-verbose
                                do {
                                    Start-Sleep -Seconds $waitShort
                                    try {
                                        $sharepointSiteOwner = Get-SPOUser -site $wizdomSiteSPO -LoginName $config.SharePointClassic.IntranetSiteCollection.Owner -ErrorAction SilentlyContinue
                                    } catch {
                                        $sharepointSiteOwner = $null
                                    }
                                } while ($null -eq $sharepointSiteOwner)
                            } catch {
                                Start-Sleep -Seconds $waitShort
                            }
                        } until ($sharepointSiteOwner -ne $null)
                        Set-SPOUser -site $wizdomSiteSPO -LoginName $config.SharePointClassic.IntranetSiteCollection.Owner -IsSiteCollectionAdmin $true |write-verbose
                    }
                }
                Write-WizdomHost -messageType WARNING -outputMaxLength $outputMaxLength -initialStringLength ($output+$dotCount) -message " - Wizdom site already exists" -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
            }
        }
#endregion

#region Create Wizdom DataStore site
        $output = Write-WizdomHost -messageType PROCESS -message ("Creating Wizdom site '" + $wizdomDataSiteSPO + "'") -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
        if ((get-sposite -Limit ALL| Where Url -eq $wizdomDataSiteSPO) -eq $null ) {
            $dotCount = 0
            $retryCount = 0
            do {
                try {
                    try {
                        $deletedSite = Get-SPODeletedSite -Identity $wizdomDataSiteSPO -ErrorAction Stop
                        if ($deletedSite -ne $null) {Remove-SPODeletedSite -Identity $wizdomDataSiteSPO -Confirm:$false}
                    } catch {}
                    if ($PSCmdlet.ShouldProcess("Wizdom Datastore", "Create Site")) {
                        New-SPOSite -Url $wizdomDataSiteSPO -Owner $SharePointTenantAdmin -StorageQuota 1024 -Title "Wizdom Datastore" -CompatibilityLevel 15 -LocaleId $config.SharePointClassic.IntranetSiteCollection.LocaleId -Template "STS#0"
                    }
                    $siteCreated = $true
                } catch {
                    $siteCreated = $false
                    if ($retryCount -eq 0) {
                        Write-host
                        $output = Write-WizdomHost -messageType PROCESS -message "SharePoint has put deletion of previous site in queue. It might take 10-15 mins for it to become available again"
                    } else {
                        if (($output + $dotCount) -le ($outputMaxLength - 2)) {
                            Write-Host -NoNewline "." -ForegroundColor White
                            $dotCount++
                        }
                    }
                    $retryCount++
                    Start-Sleep -Seconds $waitVeryLong
                }
            } until ($siteCreated)
            $pnpWizdomDataSiteAppContext = Connect-PnPOnline -ReturnConnection -Url $wizdomDataSiteSPO  -AppId $clientId -AppSecret $clientSecret
            if ($PSCmdlet.ShouldProcess($config.Office365.WizdomSearchUser.Account,"Add User as Site Collection Owner")) {
                do {
                    Start-Sleep -Seconds $waitShort
                    $ownerGroup = Get-PnPGroup -Connection $pnpWizdomDataSiteAppContext -AssociatedOwnerGroup -ErrorAction SilentlyContinue
                } until ($ownerGroup -ne $null)
                do {
                    try {                
                        Add-SPOUser -site $wizdomDataSiteSPO -LoginName $config.Office365.WizdomSearchUser.Account -group $ownerGroup.title |write-verbose
                        do {
                            Start-Sleep -Seconds $waitShort
                            try {
                                $sharepointDataSiteSearchUser = Get-SPOUser -site $wizdomDataSiteSPO -LoginName $config.Office365.WizdomSearchUser.Account -ErrorAction SilentlyContinue
                            } catch {
                                $sharepointDataSiteSearchUser = $null
                            }
                        } while ($null -eq $sharepointDataSiteSearchUser)
                    } catch {
                        Start-Sleep -Seconds $waitShort
                    }
                } until ($sharepointDataSiteSearchUser -ne $null)
            }
            Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength ($output+$dotCount) -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
            } else {
            $pnpWizdomDataSiteAppContext = Connect-PnPOnline -ReturnConnection -Url $wizdomDataSiteSPO  -AppId $clientId -AppSecret $clientSecret
            do {
                Start-Sleep -Seconds $waitShort
                $ownerGroup = Get-PnPGroup -Connection $pnpWizdomDataSiteAppContext -AssociatedOwnerGroup -ErrorAction SilentlyContinue
            } until ($ownerGroup -ne $null)
            if (@(Get-PnPGroupMembers -Identity $ownerGroup -Connection $pnpWizdomDataSiteAppContext).where({$_.LoginName -eq ("i:0#.f|membership|" + $config.SharePointClassic.IntranetSiteCollection.Owner)})[0].id -eq $null) {
                if ($installmode -ne "NoneTestConfig") {
                    if ($PSCmdlet.ShouldProcess($config.Office365.WizdomSearchUser.Account,"Add User as Site Collection Owner")) {
                        do {
                            try {
                                Add-SPOUser -site $wizdomDataSiteSPO -LoginName $config.Office365.WizdomSearchUser.Account -group $ownerGroup.title |write-verbose
                                do {
                                    Start-Sleep -Seconds $waitShort
                                    try {
                                        $sharepointDataSiteSearchUser = Get-SPOUser -site $wizdomDataSiteSPO -LoginName $config.Office365.WizdomSearchUser.Account -ErrorAction SilentlyContinue
                                    } catch {
                                        $sharepointDataSiteSearchUser = $null
                                    }
                                } while ($null -eq $sharepointDataSiteSearchUser)
                            } catch {
                                Start-Sleep -Seconds $waitShort
                            }
                        } until ($sharepointDataSiteSearchUser -ne $null)
                    }
                }
            }
            Write-WizdomHost -messageType WARNING -outputMaxLength $outputMaxLength -initialStringLength ($output+$dotCount) -message " - Wizdom site already exists" -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
        }
#endregion

#region Activating Publishing Features
        if ($environment -eq 'Classic' -or $environment -eq 'Both') {
            if (($config.Wizdom.siteCollectionBaseTemplate).tolower() -ne "publishing") {
                $output = Write-WizdomHost -messageType PROCESS -message ("Activating Publishing Features on '" + $wizdomSiteSPO + "'") -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
                if ($PSCmdlet.ShouldProcess($wizdomSiteSPO, "Activate Publishing Features")) {
                    Enable-PnPFeature -Identity "f6924d36-2fa8-4f0b-b16d-06b7250180fa" -Scope Site -Connection $pnpWizdomSiteAppContext
                    Enable-PnPFeature -Identity "94c94ca6-b32f-4da9-a9e3-1f3d343d7ecb" -Scope Web -Connection $pnpWizdomSiteAppContext
                }
                Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
            }
        }
#endregion

#region De-activating Mobile Browser View Feature
        if ($environment -eq 'Classic' -or $environment -eq 'Both') {
            $output = Write-WizdomHost -messageType PROCESS -message ("De-activating Mobile Browser View Feature on '" + $wizdomSiteSPO + "'") -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
            if ($PSCmdlet.ShouldProcess($wizdomSiteSPO, "Deactivate Mobile Browser View Feature")) {
                Disable-PnPFeature -Identity "d95c97f3-e528-4da2-ae9f-32b3535fbb59" -Scope Web -Connection $pnpWizdomSiteAppContext
            }
            Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
        }
    } catch {
        Write-WizdomHost -messageType ERROR -outputMaxLength $outputMaxLength -initialStringLength $output -afterOutputMessage $_.Exception.Message -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
        Write-Host "Site creation failed. Stopping installation script" -ForegroundColor White
        Exit
    }
#endregion

#region Creating Azure AD app
    if (((Get-AzureADApplication -All:$true).Where({$_.DisplayName -eq $config.AzureAD.WizdomApplicationName}).DisplayName -ne $null) -and ($config.AzureAD.AppId -ne "") -and ($config.AzureAD.Secret -ne "")) {
        $output = Write-WizdomHost -messageType PROCESS -message "Using existing Azure AD App '$($config.AzureAD.WizdomApplicationName)'" -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference    
            $adAppId = $config.AzureAD.AppId
            $adAppSecret = $config.AzureAD.Secret  
        Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    } else {
        $output = Write-WizdomHost -messageType PROCESS -message "Creating Azure AD App" -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
        if ($PSCmdlet.ShouldProcess($config.AzureAD.WizdomApplicationName, "Create New AD App")) {
            $wizdomAdApp = New-WizdomADApp -applicationName $config.AzureAD.WizdomApplicationName -wizdomWebsiteUrl $websiteUrl -siteCollectionUrl $wizdomSiteSPO
            do {
                Start-Sleep -Seconds $waitShort
            } until (((Get-AzureADApplication -All:$true).Where({$_.DisplayName -eq $config.AzureAD.WizdomApplicationName}).DisplayName -ne $null))
            if ($wizdomAdApp.CreationStatus -eq "Created") {
                Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
                $adAppId = $wizdomAdApp.AppId
                $adAppSecret = $wizdomAdApp.AzureAdKey            
            } elseif ($wizdomAdApp.CreationStatus -eq "NotCreated") {
                Write-WizdomHost -messageType WARNING -outputMaxLength $outputMaxLength -initialStringLength $output -message " - Azure AD Application $($config.AzureAD.WizdomApplicationName) already exists" -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
                $adAppId = $config.AzureAD.AppId
                $adAppSecret = $config.AzureAD.Secret            
            } else {
                Write-WizdomHost -messageType ERROR -outputMaxLength $outputMaxLength -initialStringLength $output -afterOutputMessage "Azure AD registration failed." -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
                Exit
            }
        }
        $output = Write-WizdomHost -messageType PROCESS -message "Granting Permissions to the Azure AD App" -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
            if ($PSCmdlet.ShouldProcess($config.AzureAD.WizdomApplicationName, "Grant Permissions and generate accessToken")) {
                Grant-OAuth2PermissionsToApp -azureAppId $adAppId -SharePointTenantAdmin $SharePointTenantAdmin -apiToken $o365ApiToken.access_token|write-verbose
                do {
                    Start-Sleep -Seconds $waitShort
                    try {
                        $accessToken = Get-WizdomAccessToken -adTenant ($config.Office365.TenantName + ".onmicrosoft.com") -sharepointTenant ($SharePointUrl+"/") -adClientId $adAppId -adClientSecret $adAppSecret -username $config.Office365.WizdomSearchUser.Account -userpassword $config.Office365.WizdomSearchUser.Password
                    } catch {
                        $accessToken = $null
                    }
                } while ($accessToken -eq $null)
            }
        Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    }
#endregion

#region Creating Azure Resource Group
    $output = Write-WizdomHost -messageType PROCESS -message  ("Creating Azure Resource Group " + $config.Azure.ResourceGroupName + ".") -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    $azureResourceGroup = Get-AzureRmResourceGroup -Name $config.Azure.ResourceGroupName -ErrorAction SilentlyContinue
    if ($null -eq $azureResourceGroup) {
        if ($PSCmdlet.ShouldProcess($config.Azure.ResourceGroupName, "Create Azure Resource Group")) {
            New-AzureRmResourceGroup -Name $config.Azure.ResourceGroupName -Location $config.Azure.Location -Force|write-verbose
        }
        Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    } else {
        Write-WizdomHost -messageType WARNING -outputMaxLength $outputMaxLength -initialStringLength $output -message " - Azure Resource Group '$($config.Azure.ResourceGroupName)' already exists" -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    }
#endregion

#region Creating Azure Services
    #deploy the template to the resource group
    $output = Write-WizdomHost -messageType PROCESS -message  "Creating Azure Services - (long running process)" -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    if ($PSCmdlet.ShouldProcess($config.Azure.ResourceGroupName, "Deploy Azure Resources to Resource Group")) {
        $groupDeploymentParameters = @{
            ResourceGroupName = $config.Azure.ResourceGroupName #"CHSdeployTest"
            websiteName = $websiteName #"chsdeploytestsite"
            hostingPlanName = $hostingPlanName #"chsdeploytestplan"
            serverName = $config.Azure.Database.ServerName #"chsdeploytestsql"
            databaseName = $AppDatabaseName #"chsdeploytestdb"
            administratorLogin = $config.Azure.Database.UserName #"chsdeploytestuser"
            administratorLoginPassword = $DBPassword #$chsdeploytestpwd
            storageAccountName = $storageAccountName #"chsdeploytestst"
        }
        switch($config.Azure.NumberOfUsers) {
            {$_ -le 99} {
                $groupDeploymentParameters.Add("TemplateFile",($PSScriptRoot + $config.Wizdom.memoryCacheTemplate))
                $groupDeploymentParameters.Add("hostingPlanSKUTier","PremiumV2")
                $groupDeploymentParameters.Add("hostingPlanSKUName","P1v2")
                #$groupDeploymentParameters.Add("hostingPlanSKUTier","Standard") - setting this afterwards, to ensure the environment gets created in a compatible rack.
                #$groupDeploymentParameters.Add("hostingPlanSKUName","S1")
                $groupDeploymentParameters.Add("hostingPlanSKUCapacity",1)
                $groupDeploymentParameters.Add("databaseServiceLevel","S0")            
                #75€
            }
            {$_ -ge 100 -and $_ -le 500} {
                $groupDeploymentParameters.Add("TemplateFile",($PSScriptRoot + $config.Wizdom.memoryCacheTemplate))
                $groupDeploymentParameters.Add("hostingPlanSKUTier","PremiumV2")
                $groupDeploymentParameters.Add("hostingPlanSKUName","P1v2")
                $groupDeploymentParameters.Add("hostingPlanSKUCapacity",1)
                $groupDeploymentParameters.Add("databaseServiceLevel","S0")
                #140€            
            }
            {$_ -ge 501 -and $_ -le 2000} {
                $groupDeploymentParameters.Add("TemplateFile",($PSScriptRoot + $config.Wizdom.memoryCacheTemplate))
                $groupDeploymentParameters.Add("hostingPlanSKUTier","PremiumV2")
                $groupDeploymentParameters.Add("hostingPlanSKUName","P1v2")
                $groupDeploymentParameters.Add("hostingPlanSKUCapacity",1) 
                $groupDeploymentParameters.Add("databaseServiceLevel","S0")
                #140€
            }
            {$_ -ge 2001 -and $_ -le 5000} {
                #Remember to also change limit in the check config section (approximately line 925)
                $groupDeploymentParameters.Add("TemplateFile",($PSScriptRoot + $config.Wizdom.memoryCacheTemplate))
                $groupDeploymentParameters.Add("hostingPlanSKUTier","PremiumV2")
                $groupDeploymentParameters.Add("hostingPlanSKUName","P2v2")
                $groupDeploymentParameters.Add("hostingPlanSKUCapacity",1)
                $groupDeploymentParameters.Add("databaseServiceLevel","S0")
                #225€            
            }
            {$_ -ge 5001 -and $_ -le 10000} {
                $groupDeploymentParameters.Add("TemplateFile",($PSScriptRoot + $config.Wizdom.redisCacheTemplate))
                $groupDeploymentParameters.Add("redisCacheName",$redisCacheName)
                $groupDeploymentParameters.Add("redisCacheSKU","Standard")
                $groupDeploymentParameters.Add("redisCacheFamily","C")
                $groupDeploymentParameters.Add("redisCacheCapacity",1)
                $groupDeploymentParameters.Add("hostingPlanSKUTier","PremiumV2")
                $groupDeploymentParameters.Add("hostingPlanSKUName","P1v2")
                $groupDeploymentParameters.Add("hostingPlanSKUCapacity",1) #1 core ok?
                $groupDeploymentParameters.Add("databaseServiceLevel","S0")
                #225€            
            }
            {$_ -ge 10001 -and $_ -le 25000} {
                $groupDeploymentParameters.Add("TemplateFile",($PSScriptRoot + $config.Wizdom.redisCacheTemplate))
                $groupDeploymentParameters.Add("redisCacheName",$redisCacheName)
                $groupDeploymentParameters.Add("redisCacheSKU","Standard")
                $groupDeploymentParameters.Add("redisCacheFamily","C")
                $groupDeploymentParameters.Add("redisCacheCapacity",2)
                $groupDeploymentParameters.Add("hostingPlanSKUTier","PremiumV2")
                $groupDeploymentParameters.Add("hostingPlanSKUName","P1v2")
                $groupDeploymentParameters.Add("hostingPlanSKUCapacity",1) #1 core ok?
                $groupDeploymentParameters.Add("databaseServiceLevel","S1")
                #290€          
            }
            {$_ -ge 25001} {
                $groupDeploymentParameters.Add("TemplateFile",($PSScriptRoot + $config.Wizdom.redisCacheTemplate))
                $groupDeploymentParameters.Add("redisCacheName",$redisCacheName)
                $groupDeploymentParameters.Add("redisCacheSKU","Standard")
                $groupDeploymentParameters.Add("redisCacheFamily","C")
                $groupDeploymentParameters.Add("redisCacheCapacity",2)
                $groupDeploymentParameters.Add("hostingPlanSKUTier","PremiumV2")
                $groupDeploymentParameters.Add("hostingPlanSKUName","P1v2")
                $groupDeploymentParameters.Add("hostingPlanSKUCapacity",1) #1 core ok?
                $groupDeploymentParameters.Add("databaseServiceLevel","S2")   
                #325€        
            }           
            }    
        New-AzureRmResourceGroupDeployment @groupDeploymentParameters|write-verbose
        
        if ($config.Azure.NumberOfUsers -le 99) {
            Set-AzureRmAppServicePlan -Tier Standard -ResourceGroupName $config.Azure.ResourceGroupName -Name $hostingPlanName -WorkerSize Small -AsJob|write-verbose
        } 
<#
Test Script for new deployment options:

$testdeploymentname = "chsdeploytest"
$groupDeploymentsqlpwd = "asdSDF123!#¤%" | ConvertTo-SecureString -AsPlainText -Force

$groupDeploymentParameters = @{
ResourceGroupName = $testdeploymentname
TemplateFile = ".\SharePointOnlineInstallationSupportFiles\template_MemoryCache.json"
websiteName = ($testdeploymentname + "site")
hostingPlanName = ($testdeploymentname + "plan")
serverName = ($testdeploymentname + "sql")
databaseName = ($testdeploymentname + "db")
administratorLogin = ($testdeploymentname + "user")
administratorLoginPassword = $groupDeploymentsqlpwd
storageAccountName = ($testdeploymentname + "st")
}
$groupDeploymentParameters.Add("hostingPlanSKUTier","PremiumV2")
$groupDeploymentParameters.Add("hostingPlanSKUName","P1v2")
$groupDeploymentParameters.Add("hostingPlanSKUCapacity",1)
$groupDeploymentParameters.Add("databaseServiceLevel","S0")            



$groupDeploymentParameters = @{
ResourceGroupName = $testdeploymentname
TemplateFile = ".\SharePointOnlineInstallationSupportFiles\template_RedisCache.json"
websiteName = ($testdeploymentname + "site")
hostingPlanName = ($testdeploymentname + "plan")
serverName = ($testdeploymentname + "sql")
databaseName = ($testdeploymentname + "db")
administratorLogin = ($testdeploymentname + "user")
administratorLoginPassword = $groupDeploymentsqlpwd
storageAccountName = ($testdeploymentname + "st")
redisCacheName = ($testdeploymentname + "redis")
}
$groupDeploymentParameters.Add("redisCacheSKU","Standard")
$groupDeploymentParameters.Add("redisCacheFamily","C")
$groupDeploymentParameters.Add("redisCacheCapacity",1)
$groupDeploymentParameters.Add("hostingPlanSKUTier","PremiumV2")
$groupDeploymentParameters.Add("hostingPlanSKUName","P1v2")
$groupDeploymentParameters.Add("hostingPlanSKUCapacity",1)
$groupDeploymentParameters.Add("databaseServiceLevel","S0")

New-AzureRmResourceGroup -Name $testdeploymentname -Location "West Europe"
New-AzureRmResourceGroupDeployment @groupDeploymentParameters

#>

    }
    Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
#endregion

#region Creating Applications Insights Service
    if ($installMode -match "AppInsights") {
        $output = Write-WizdomHost -messageType PROCESS -message "Creating Applications Insights Service" -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
        try {
            if ($PSCmdlet.ShouldProcess($config.Azure.ResourceGroupName, "Deploy Azure Resources to Resource Group")) {
                New-AzureRmResourceGroupDeployment -ResourceGroupName $config.Azure.ResourceGroupName -TemplateFile ($PSScriptRoot + $config.Wizdom.applicationsInsightTemplate) -appName $applicationInsightsName -appLocation $config.Azure.ApplicationInsights.Location |write-verbose
                $applicationInsights = Get-AzureRmApplicationInsights -ResourceGroupName $config.Azure.ResourceGroupName -Name $applicationInsightsName
            }
            Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
        } catch {
            Write-WizdomHost -messageType WARNING -outputMaxLength $outputMaxLength -initialStringLength $output -afterOutputMessage $_.Exception.Message -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
            $applicationInsights = $null
        }
    }
#endregion

#region Creating KeyVault Service
if ($config.Azure.KeyVault.enableKeyVault) {
    $output = Write-WizdomHost -messageType PROCESS -message "Creating Azure KeyVault Service" -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    try {
        if ($PSCmdlet.ShouldProcess($keyVaultName, "Create new KeyVault Service")) {
            $keyVault = New-AzureRmKeyVault -Name $keyVaultName -ResourceGroupName $config.Azure.ResourceGroupName -Location $config.Azure.KeyVault.location
            $webapp = Set-AzureRmWebApp -AssignIdentity $true -ResourceGroupName $config.Azure.ResourceGroupName -Name $websiteName
            do {
                Start-Sleep -Seconds $waitShort
            } until ($null -ne (Get-AzureRmADServicePrincipal -ObjectId $webapp.Identity.PrincipalId))
            Start-Sleep -Seconds $waitShort #Been seen to fail set-azurermkeyvaultaccesspolicy with object not found, even though it's just been found with get-azurermadserviceprincipal...
            Set-AzureRmKeyVaultAccessPolicy -VaultName $keyVaultName -ResourceGroupName $config.Azure.ResourceGroupName -PermissionsToSecrets get -ObjectId $webapp.Identity.PrincipalId|Write-Verbose
        }
        Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    } catch {
        Write-WizdomHost -messageType WARNING -outputMaxLength $outputMaxLength -initialStringLength $output -afterOutputMessage $_.Exception.Message -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    }
}
#endregion

#region Updating Azure Webapplication Application Settings and generat outputfile
    $output = Write-WizdomHost -messageType PROCESS -message "Updating Azure Webapplication Application Settings" -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    if ($PSCmdlet.ShouldProcess($websiteName, "Update Azure WebApp AppSettings")) {
        $AzureWebAppSiteConfig = (Get-AzureRMWebApp -ResourceGroupName $config.Azure.ResourceGroupName -Name $websiteName).SiteConfig
        $AppSettingsList = @{}
        ForEach ($kvp in $AzureWebAppSiteConfig.AppSettings) {
            $AppSettingsList[$kvp.Name] = $kvp.Value
        }
        $AppSettingsList.AzureADClientID = $adAppId
        if ($config.Azure.KeyVault.enableKeyVault -and $config.Azure.KeyVault.Secrets.AzureAppClientSecret) {
            $AzureAppClientSecret = ConvertTo-SecureString $adAppSecret -AsPlainText -Force
            $newVaultSecret = Set-AzureKeyVaultSecret -VaultName $keyVaultName -Name "AzureAdKey" -SecretValue $AzureAppClientSecret
            $AppSettingsList.AzureAdKey = "AZURE_KEY_VAULT:AzureAdKey"
        } else {
            $AppSettingsList.AzureADKey = $adAppSecret
        }
        $AppSettingsList.ClientId = $clientId.ToString()
        if ($config.Azure.KeyVault.enableKeyVault -and $config.Azure.KeyVault.Secrets.SharePointAppClientSecret) {
            $SharePointAppClientSecret = ConvertTo-SecureString $clientSecret -AsPlainText -Force
            $newVaultSecret = Set-AzureKeyVaultSecret -VaultName $keyVaultName -Name "ClientSecret" -SecretValue $SharePointAppClientSecret
            $AppSettingsList.ClientSecret = "AZURE_KEY_VAULT:ClientSecret"
        } else {$AppSettingsList.ClientSecret = $clientSecret}
        $AppSettingsList.AdminSiteUrl = $SharePointAdminUrl
        $AppSettingsList.AzureTenantname = $config.Office365.TenantName + ".onmicrosoft.com"
        $AppSettingsList.SearchUsername = $config.Office365.WizdomSearchUser.Account
        $WizdomSearchUserPassword =  $config.Office365.WizdomSearchUser.Password
        $WizdomSearchUserPasswordClearText = $WizdomSearchUserPassword
        if ($config.Azure.KeyVault.enableKeyVault -and $config.Azure.KeyVault.Secrets.WizdomSearchUserPassword) {
            $WizdomSearchUserPassword = ConvertTo-SecureString $WizdomSearchUserPassword -AsPlainText -Force
            $newVaultSecret = Set-AzureKeyVaultSecret -VaultName $keyVaultName -Name "SearchPassword" -SecretValue $WizdomSearchUserPassword
            $AppSettingsList.SearchPassword = "AZURE_KEY_VAULT:SearchPassword"
        } else {$AppSettingsList.SearchPassword = $WizdomSearchUserPassword}
        $AppSettingsList.TenantSiteUrl = "https://" + $config.Office365.TenantName + ".sharepoint.com/"
        $AppSettingsList.GraphUser = $config.Office365.WizdomGraphUser.Account
        $WizdomGraphUserPassword = $config.Office365.WizdomGraphUser.Password
        $WizdomGraphUserPasswordClearText = $WizdomGraphUserPassword
        if ($config.Azure.KeyVault.enableKeyVault -and $config.Azure.KeyVault.Secrets.WizdomGraphUserPassword) {
            $WizdomGraphUserPassword = ConvertTo-SecureString $WizdomGraphUserPassword -AsPlainText -Force
            $newVaultSecret = Set-AzureKeyVaultSecret -VaultName $keyVaultName -Name "GraphUserPassword" -SecretValue $WizdomGraphUserPassword
            $AppSettingsList.GraphUserPassword = "AZURE_KEY_VAULT:GraphUserPassword"
        } else {$AppSettingsList.GraphUserPassword = $WizdomGraphUserPassword}
        if ($installMode -match "AppInsights") {
            if ($applicationInsights -ne $null) {
                $AppSettingsList['ApplicationInsightsInstrumentationKey'] = $applicationInsights.InstrumentationKey
                $AppSettingsList['ApplicationInsightsUsername'] = $config.Office365.ApplicationInsightsUser.Account 
                $WizdomApplicationInsightsUserPassword = ($config.Office365.ApplicationInsightsUser.Password).Replace("&","&amp;").Replace("<","&lt;").Replace(">","&gt;").Replace("""","&quot;").Replace("'","&apos;")
                $WizdomApplicationInsightsUserPasswordClearText = $WizdomApplicationInsightsUserPassword
                if ($config.Azure.KeyVault.enableKeyVault -and $config.Azure.KeyVault.Secrets.WizdomApplicationInsightsUserPassword) {
                    $WizdomApplicationInsightsUserPassword = ConvertTo-SecureString $WizdomApplicationInsightsUserPassword -AsPlainText -Force
                    $newVaultSecret = Set-AzureKeyVaultSecret -VaultName $keyVaultName -Name "ApplicationInsightsPassword" -SecretValue $WizdomApplicationInsightsUserPassword
                    $AppSettingsList['ApplicationInsightsPassword'] = "AZURE_KEY_VAULT:ApplicationInsightsPassword"
                } else {$AppSettingsList['ApplicationInsightsPassword'] = $WizdomApplicationInsightsUserPassword}

                #Add to output file
                $outFileArrayAnalytics += New-Object PSObject -Property @{
                Line = 2
                Field = "Instrumentation Key"
                Input = $AppSettingsList.ApplicationInsightsInstrumentationKey}
                $outFileArrayAnalytics += New-Object PSObject -Property @{
                Line = 3
                Field = "AI Service Account Reader User"
                Input = $AppSettingsList.ApplicationInsightsUsername}
                $outFileArrayAnalytics += New-Object PSObject -Property @{
                Line = 4
                Field = "AI Service Account Reader Password"
                Input = $WizdomApplicationInsightsUserPasswordClearText}

            }
        }
        if ($config.Azure.KeyVault.enableKeyVault) {
            $AppSettingsList['KeyvaultUrl'] = $keyVault.VaultUri
        }
        $AppSettingsList['HandleConfigurationTokenExpired'] = "true"
        
        #Add to output file
        $outFileArrayBasic += New-Object PSObject -Property @{
        Line = 1
        Field = "ClientId"
        Input = $AppSettingsList.ClientId}
        $outFileArrayBasic += New-Object PSObject -Property @{
        Line = 2
        Field = "ClientSecret"
        Input = $clientSecret}
        $outFileArrayBasic += New-Object PSObject -Property @{
        Line = 3
        Field = "SecondaryClientSecret"
        Input = ""}
        $outFileArrayBasic += New-Object PSObject -Property @{
        Line = 4
        Field = "AzureTenantName"
        Input = $AppSettingsList.AzureTenantname}
        $outFileArrayBasic += New-Object PSObject -Property @{
        Line = 5
        Field = "AzureADClientID"
        Input = $AppSettingsList.AzureADClientID}
        $outFileArrayBasic += New-Object PSObject -Property @{
        Line = 6
        Field = "AzureADKey"
        Input = $AppSettingsList.AzureADKey}
        $outFileArrayBasic += New-Object PSObject -Property @{
        Line = 7
        Field = "TenantSiteUrl"
        Input = $AppSettingsList.TenantSiteUrl}
        $outFileArrayBasic += New-Object PSObject -Property @{
        Line = 8
        Field = "AdminSiteUrl"
        Input = $AppSettingsList.AdminSiteUrl}
        $outFileArrayBasic += New-Object PSObject -Property @{
        Line = 13
        Field = "GraphUser"
        Input = $AppSettingsList.GraphUser}
        $outFileArrayBasic += New-Object PSObject -Property @{
        Line = 14
        Field = "GraphUserPassword"
        Input = $WizdomGraphUserPasswordClearText}
        $outFileArrayBasic += New-Object PSObject -Property @{
        Line = 18
        Field = "SearchUser"
        Input = $AppSettingsList.SearchUsername}
        $outFileArrayBasic += New-Object PSObject -Property @{
        Line = 19
        Field = "SearchPassword"
        Input = $WizdomSearchUserPasswordClearText}
        
        Set-AzureRmWebApp -ResourceGroupName $config.Azure.ResourceGroupName -Name $websiteName -AppSettings $AppSettingsList |write-verbose
    }
    Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
#endregion

#region Uploading Wizdom Maintenance Timer Job (Azure Website WebJob)
    $output = Write-WizdomHost -messageType PROCESS -message "Uploading Wizdom Maintenance Timer Job (Azure Website WebJob)" -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    if ($installmode -ne "NoneTestConfig") {
        $accessTokenKudu = Get-KuduApiAuthorisationHeaderValue $config.Azure.ResourceGroupName $websiteName -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    }
    #Generating header to create and publish the Webjob :
    $Header = @{
    'Content-Disposition'='attachment; filename=' + $config.Wizdom.timerJobZip.split('\')[-1]
    'Authorization'=$accessTokenKudu
            }
    $apiUrl = "https://" + $websiteName + ".scm.azurewebsites.net/api/triggeredwebjobs/WizdomMaintenanceTimerJob"
    $webJobFile = $PSScriptRoot + $config.Wizdom.timerJobZip
    if ($PSCmdlet.ShouldProcess("WizdomMaintenanceTimerJob", "Upload")) {
        do {
            try {
                $result = Invoke-RestMethod -Uri $apiUrl -Headers $Header -Method put -InFile $webJobFile -ContentType 'application/zip' -TimeoutSec 30
                $uploadSuccessful = $true
            } catch {
                Start-Sleep -Seconds $waitShort
                $uploadSuccessful = $false
            }
        } until ($uploadSuccessful)
        Invoke-AzureRmResourceAction -ApiVersion "2015-08-01"  -ResourceGroupName $config.Azure.ResourceGroupName -ResourceName ($websiteName + "/WizdomMaintenanceTimerJob") -ResourceType microsoft.web/sites/triggeredwebjobs -Action run -Force
    }
    Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
#endregion

#region Downloading the Azure Website Pulishing Profile and Deploying Wizdom
    $output = Write-WizdomHost -messageType PROCESS -message "Downloading the Azure Website Pulishing Profile and Deploying Wizdom - (long running process)" -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    if ($PSCmdlet.ShouldProcess($websiteName, "Get Publishing Profile")) {
        Get-AzureRMWebAppPublishingProfile -ResourceGroupName $config.Azure.ResourceGroupName -Name $websiteName |Out-File -FilePath ($websiteName+".publishingSettings") -Force
    }

    try {
        if ($PSCmdlet.ShouldProcess($websiteName, "Deploy Wizdom")) {
            .\Deploy.ps1 -publishingprofile ($websiteName+".publishingSettings") -OpenDeploymentInNewWindow|write-verbose
        }
        Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    } catch {
        Write-WizdomHost -messageType ERROR -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
        Write-Host
        Write-Host "Something went wrong with the Wizdom installation:" -ForegroundColor white
        Write-Host ("Please try installing Wizdom using '.\Deploy.ps1 -publishingprofile (" + $websiteName + """.publishingSettings"")' to get error details") -ForegroundColor White
        Exit
    }
#endregion

#region Generating and uploading the SharePoint App package
    $output = Write-WizdomHost -messageType PROCESS -message "Generating and uploading the SharePoint App package" -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    $wizdomAppProductId = New-Guid
    $pnpAppCatalogAppContext = Connect-PnPOnline -Url $appCatalogUrl  -AppId $clientId -AppSecret $clientSecret -ReturnConnection
    if ($PSCmdlet.ShouldProcess($config.SharePointClassic.WizdomApp.Title, "Create Wizdom App Package")) {
        New-WizdomAppPackage -clientid $clientId -appurl $websiteUrl -outfile $config.SharePointClassic.WizdomApp.FileName -title $config.SharePointClassic.WizdomApp.Title -name $config.SharePointClassic.WizdomApp.Name -productid $wizdomAppProductId |write-verbose
    }
    if ($PSCmdlet.ShouldProcess($config.SharePointClassic.WizdomApp.Title, "Upload Wizdom App Package")) {
        $WizdomSPApp = Add-PnPApp -Path $config.SharePointClassic.WizdomApp.FileName -Scope Tenant -Overwrite -Connection $pnpAppCatalogAppContext
        Remove-Item -Path $config.SharePointClassic.WizdomApp.FileName -Force -ErrorAction Ignore
    }
    Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
#endregion

#region Restarting Azure Web App Service
    $output = Write-WizdomHost -messageType PROCESS -message "Restarting Azure Web App Service" -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    if ($PSCmdlet.ShouldProcess($websiteName, "Restart")) {
        Stop-AzureRmWebApp -ResourceGroupName $config.Azure.ResourceGroupName -Name $websiteName|write-verbose
        Start-AzureRmWebApp -ResourceGroupName $config.Azure.ResourceGroupName -Name $websiteName|write-verbose
    }
    Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
#endregion

#region Waiting for Wizdom to complete installation (creating DB content etc)
    $output = Write-WizdomHost -messageType PROCESS -message "Waiting for Wizdom to complete installation (creating DB content etc) - (long running process)" -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    $dotCount = 0
    if ($PSCmdlet.ShouldProcess($websiteName, "Install")) {
        do {
        Start-Sleep -Seconds $waitVeryLong
        if (($output + $dotCount) -le ($outputMaxLength - 2)) {
            Write-Host -NoNewline "." -ForegroundColor White
            $dotCount++
        }
        } until ((ping-wizdom -wizdomUrl $websiteUrl -accessToken $accessToken))
        $accessToken = Get-WizdomAccessToken -adTenant ($config.Office365.TenantName + ".onmicrosoft.com") -sharepointTenant ($SharePointUrl+"/") -adClientId $adAppId -adClientSecret $adAppSecret -username $config.Office365.WizdomSearchUser.Account -userpassword $config.Office365.WizdomSearchUser.Password
    }
    Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength ($output+$dotCount) -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
#endregion

#region Transfering connectionstrings to Azure Keyvault
    if ($config.Azure.KeyVault.enableKeyVault -and ($config.Azure.KeyVault.Secrets.SQLConnectionString -or $config.Azure.KeyVault.Secrets.BlobStorageConnectionString -or $config.Azure.KeyVault.Secrets.Wizdom365BusinessAppEntitiesConnectioString -or $config.Azure.KeyVault.Secrets.WizdomContainerConnectionString)) {
        $output = Write-WizdomHost -messageType PROCESS -message "Transfering connection strings to Azure Keyvault" -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    
        $ConnectionStringList = New-Object System.Collections.HashTable
        if ($config.Azure.KeyVault.Secrets.SQLConnectionString) {
            $SQLConnectionString = ConvertTo-SecureString $AzureWebAppSiteConfig.ConnectionStrings.where({$_.Name -eq 'DefaultConnection'}).ConnectionString -AsPlainText -Force
            $newVaultSecret = Set-AzureKeyVaultSecret -VaultName $keyVaultName -Name "DefaultConnection" -SecretValue $SQLConnectionString
            $ConnectionString = "AZURE_KEY_VAULT:DefaultConnection"
        } else {
            $connectionString = $AzureWebAppSiteConfig.ConnectionStrings.where({$_.Name -eq 'DefaultConnection'}).ConnectionString
        }
        $valuePair = @{Type=$AzureWebAppSiteConfig.ConnectionStrings.where({$_.Name -eq 'DefaultConnection'}).Type.ToString();Value=$ConnectionString}
        $ConnectionStringList.Add("DefaultConnection",$valuePair)
        $outFileArrayConnectionStrings += New-Object PSObject -Property @{
            Line = 1
            Field = "DefaultConnection"
            Input = $AzureWebAppSiteConfig.ConnectionStrings.where({$_.Name -eq 'DefaultConnection'}).ConnectionString}

        if ($config.Azure.KeyVault.Secrets.BlobStorageConnectionString) {
            $BlobStorageConnectionString = ConvertTo-SecureString $AzureWebAppSiteConfig.ConnectionStrings.where({$_.Name -eq 'BlobStorageConnection'}).ConnectionString -AsPlainText -Force
            $newVaultSecret = Set-AzureKeyVaultSecret -VaultName $keyVaultName -Name "BlobStorageConnection" -SecretValue $BlobStorageConnectionString
            $ConnectionString = "AZURE_KEY_VAULT:BlobStorageConnection"
        } else {
            $connectionString = $AzureWebAppSiteConfig.ConnectionStrings.where({$_.Name -eq 'BlobStorageConnection'}).ConnectionString
        }
        $valuePair = @{Type=$AzureWebAppSiteConfig.ConnectionStrings.where({$_.Name -eq 'BlobStorageConnection'}).Type.ToString();Value=$connectionString}
        $ConnectionStringList.Add("BlobStorageConnection",$valuePair)
        $outFileArrayConnectionStrings += New-Object PSObject -Property @{
            Line = 1
            Field = "BlobStorageConnection"
            Input = $AzureWebAppSiteConfig.ConnectionStrings.where({$_.Name -eq 'BlobStorageConnection'}).ConnectionString}

        if ($config.Azure.KeyVault.Secrets.Wizdom365BusinessAppEntitiesConnectioString) {
            $Wizdom365BusinessAppEntitiesConnectioString = ConvertTo-SecureString $AzureWebAppSiteConfig.ConnectionStrings.where({$_.Name -eq 'Wizdom365BusinessAppEntities'}).ConnectionString -AsPlainText -Force
            $newVaultSecret = Set-AzureKeyVaultSecret -VaultName $keyVaultName -Name "Wizdom365BusinessAppEntities" -SecretValue $Wizdom365BusinessAppEntitiesConnectioString
            $ConnectionString = "AZURE_KEY_VAULT:Wizdom365BusinessAppEntities"
        } else {
            $connectionString = $AzureWebAppSiteConfig.ConnectionStrings.where({$_.Name -eq 'Wizdom365BusinessAppEntities'}).ConnectionString
        }
        $valuePair = @{Type=$AzureWebAppSiteConfig.ConnectionStrings.where({$_.Name -eq 'Wizdom365BusinessAppEntities'}).Type.ToString();Value=$connectionString}
        $ConnectionStringList.Add("Wizdom365BusinessAppEntities",$valuePair)
        $outFileArrayConnectionStrings += New-Object PSObject -Property @{
            Line = 1
            Field = "Wizdom365BusinessAppEntities"
            Input = $AzureWebAppSiteConfig.ConnectionStrings.where({$_.Name -eq 'Wizdom365BusinessAppEntities'}).ConnectionString}

        if ($config.Azure.KeyVault.Secrets.WizdomContainerConnectionString) {
            $WizdomContainerConnectionString = ConvertTo-SecureString $AzureWebAppSiteConfig.ConnectionStrings.where({$_.Name -eq 'WizdomContainer'}).ConnectionString -AsPlainText -Force
            $newVaultSecret = Set-AzureKeyVaultSecret -VaultName $keyVaultName -Name "WizdomContainer" -SecretValue $WizdomContainerConnectionString
            $ConnectionString = "AZURE_KEY_VAULT:WizdomContainer"
        } else {
            $connectionString = $AzureWebAppSiteConfig.ConnectionStrings.where({$_.Name -eq 'WizdomContainer'}).ConnectionString
        }
        $valuePair = @{Type=$AzureWebAppSiteConfig.ConnectionStrings.where({$_.Name -eq 'WizdomContainer'}).Type.ToString();Value=$connectionString}
        $ConnectionStringList.Add("WizdomContainer",$valuePair)
        $outFileArrayConnectionStrings += New-Object PSObject -Property @{
            Line = 1
            Field = "WizdomContainer"
            Input = $AzureWebAppSiteConfig.ConnectionStrings.where({$_.Name -eq 'WizdomContainer'}).ConnectionString}

        Set-AzureRmWebApp -ResourceGroupName $config.Azure.ResourceGroupName -Name $websiteName -ConnectionStrings $ConnectionStringList |write-verbose
        Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference


        #region Restarting Azure Web App Service
            $output = Write-WizdomHost -messageType PROCESS -message "Restarting Azure Web App Service" -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
            $dotCount = 0
            if ($PSCmdlet.ShouldProcess($websiteName, "Restart")) {
                Stop-AzureRmWebApp -ResourceGroupName $config.Azure.ResourceGroupName -Name $websiteName|write-verbose
                    Write-Host -NoNewline "." -ForegroundColor White
                    $dotCount++
                Start-AzureRmWebApp -ResourceGroupName $config.Azure.ResourceGroupName -Name $websiteName|write-verbose
                    Write-Host -NoNewline "." -ForegroundColor White
                    $dotCount++

                do {
                Start-Sleep -Seconds $waitShort
                if (($output + $dotCount) -le ($outputMaxLength - 2)) {
                    Write-Host -NoNewline "." -ForegroundColor White
                    $dotCount++
                }
                } until ((ping-wizdom -wizdomUrl $websiteUrl -accessToken $accessToken))
                Start-Sleep -Seconds $waitVeryLong
                $accessToken = Get-WizdomAccessToken -adTenant ($config.Office365.TenantName + ".onmicrosoft.com") -sharepointTenant ($SharePointUrl+"/") -adClientId $adAppId -adClientSecret $adAppSecret -username $config.Office365.WizdomSearchUser.Account -userpassword $config.Office365.WizdomSearchUser.Password
            }
            Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength ($output+$dotCount) -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
        #endregion

    }
#endregion

#region Installing Wizdom on site collection
    if ($environment -eq 'Classic' -or $environment -eq 'Both') {
        $output = Write-WizdomHost -messageType PROCESS -message ("Installing Wizdom on site collection " + $wizdomSiteSPO + " - (long running process)") -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
        Login-AzureRmAccount -TenantId $tenantIdOffice365 -AccessToken $o365Token -AccountId $o365Context.Context.Account.Id -WhatIf:$false|write-verbose
        if ($PSCmdlet.ShouldProcess($wizdomSiteSPO, "Install Wizdom in Site Collection")) {
            $InstallStatus = Install-WizdomOnSitecollection -WizdomUrl $websiteUrl -sharePointUrl $wizdomSiteSPO -accessToken $accessToken
            $dotCount = 0
            do {
                Start-Sleep -Seconds $waitLong
                if (($output + $dotCount) -le ($outputMaxLength - 2)) {
                    Write-Host -NoNewline "." -ForegroundColor White
                    $dotCount++
                }
            } until ((Get-WizdomInstallStatus -WizdomUrl $websiteUrl -siteCollectionId $InstallStatus.sitecollectionId -accessToken $accessToken) -eq "Done")
        }
        Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength ($output+$dotCount) -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    }
#endregion

#region Setting Tenant level search configuration (managed properties)
    Write-Host "Settings are not overridden, so manual action may be required on tenants with preexisting configuration:" -ForegroundColor DarkGray
    $output = Write-WizdomHost -messageType PROCESS -message "Setting Tenant level search configuration (managed properties)" -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    try {
        if ($PSCmdlet.ShouldProcess("SearchConfiguration", "Upload")) {
            set-PnPSearchConfiguration -path ($PSScriptRoot + $config.Wizdom.searchConfigTemplate) -Scope Subscription -Connection $pnpSharePointAdminAppContext -ErrorAction Stop
        }
        Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    } catch {
        Write-WizdomHost -messageType WARNING -outputMaxLength $outputMaxLength -initialStringLength $output -afterOutputMessage $_.exception.message -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    }
#endregion

#region Creating default.aspx page in the pages library on SharePoint
    if ($environment -eq 'Classic' -or $environment -eq 'Both') {
        if (($config.Wizdom.siteCollectionBaseTemplate).tolower() -ne "publishing") {
            $output = Write-WizdomHost -messageType PROCESS -message "Creating default.aspx page in the pages library on SharePoint, as this is missing after activating the publishing features" -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
            try {
                if ($PSCmdlet.ShouldProcess("default.aspx", "Create")) {
                    Start-Sleep -Seconds $waitLong # Pause to ensure the SPList object has caught up with the page layout creation.
                    Add-PnPPublishingPage -PageName "default" -Title "Wizdom Home" -Publish -PageTemplateName "Wizdom2Column" -Connection $pnpWizdomSiteAppContext -ErrorAction SilentlyContinue
                    Set-PnPHomePage -RootFolderRelativeUrl "Pages/default.aspx" -Connection $pnpWizdomSiteAppContext
                }
                Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
            } catch {
                Write-WizdomHost -messageType ERROR -outputMaxLength $outputMaxLength -initialStringLength $output -afterOutputMessage $_.Exception.Message -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
            }
        }
    }
#endregion

#region Pairing Wizdom server with the Wizdom License Service
    $output = Write-WizdomHost -messageType PROCESS -message "Pairing Wizdom server with the Wizdom License Service" -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    if ($PSCmdlet.ShouldProcess("Wizdom License", "Input")) {
        do {
            if (($wizdomLicensePortalUser -ne "") -and ($config.Wizdom.LicenseID -ne "")) {
                try {
                    $nodejsDir = Join-Path -Path $env:ProgramFiles -ChildPath nodejs
                    $puppeteerScript = $PSScriptRoot + $config.Wizdom.licensePairPuppeteerScript + ' ' + $config.Wizdom.LicenseID + ' ' + $credentialWizdomLicensePortal.UserName + ' ' + $credentialWizdomLicensePortal.GetNetworkCredential().Password
                    $cmd = 'node'
                    $inner = "$puppeteerScript -NoNewWindow -Command cd '$nodejsDir'"
                    $stdOutTempFile = "$env:TEMP\$((New-Guid).Guid)"
                    $cmdResult = Start-Process $cmd -ArgumentList $inner -RedirectStandardOutput $stdOutTempFile -Wait
                    $cmdOutput = Get-Content -Path $stdOutTempFile -Raw | ConvertFrom-Json
                    $wizdomPIN = $cmdOutput.pin
                } catch {
                } finally {
                    Remove-Item -Path $stdOutTempFile -Force -ErrorAction Ignore
                }
            } else {
                Write-Host
                $wizdomPIN = read-Host "Enter license PIN from Wizdom. Type EXIT to continue without a PIN."
            }
            if ($wizdomPIN -ne "EXIT") {
                $licenseResponse = Set-WizdomPIN -WizdomUrl $websiteUrl -pin $wizdomPIN -accessToken $accessToken
            } else {
                $licenseResponse = "Continuing without PIN" 
            } 
            if ($licenseResponse -eq "Got updated license") {
                if ($wizdomLicensePortalUser -ne "") {
                    Write-Host ("." * [math]::max(($outputMaxLength - $output),0)) -ForegroundColor White -NoNewline
                }
                $licenseCheck = $true
                Write-Host "OK" -ForegroundColor Green
            } elseif ($licenseResponse -eq "Continuing without PIN") {
                Write-Host "WARNING" -ForegroundColor Yellow -NoNewline
                Write-Host (" - " + $licenseResponse) -ForegroundColor White
                $licenseCheck = $true
            } else {
                $output2 = ' - ' + $licenseResponse + ' '
                if ($outputMaxLength - $output - $output2.Length - 5 -ge 0) {
                    Write-Host $output2 -ForegroundColor Yellow -NoNewline
                    if ($wizdomLicensePortalUser -ne "") {
                        Write-Host ("." * [math]::max(($outputMaxLength - $output - $output2.Length - 5),0)) -ForegroundColor White -NoNewline
                    }
                    Write-Host "WARNING" -ForegroundColor Yellow
                    $licenseCheck = $false
                }
            }
        } while (-not $licenseCheck)
    }
#endregion

#region Deploy Modern Packages
    if ($environment -eq 'Modern' -or $environment -eq 'Both') {
        $output = Write-WizdomHost -messageType PROCESS -message "Deploying Modern Packages" -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
        if ($PSCmdlet.ShouldProcess("Wizdom Modern", "Deploy")) {
            try {
                if ($licenseResponse -ne "Continuing without PIN") {
                    Set-WizdomModernPackageAssociation -WizdomUrl $websiteUrl -accessToken $accessToken
                    Install-WizdomModernPackages -WizdomUrl $websiteUrl -accessToken $accessToken
                    $modernDeployed = $true
                } else {
                    $modernDeployed = $false
                }
            } catch {
                $modernDeployed = $false
                $deploymentError = $_.Exception.Message
            }
        }
        if ($modernDeployed) {
            Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
        } else {
            Write-WizdomHost -messageType ERROR -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference -afterOutputMessage $deploymentError
        }
    }
#region

#region Configuring Wizdom
    $output = Write-WizdomHost -messageType PROCESS -message "Configuring Wizdom" -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    try {
        if ($wizdomPIN -ne "EXIT") {
            if ($PSCmdlet.ShouldProcess("Wizdom", "Update")) {
                try {
                    $wizdomConfig = Get-WizdomConfig -WizdomUrl $websiteUrl -accessToken $accessToken
                    $wizdomConfig.Servicemenu.Toolbar.'#text' = "FloatingBottomRight"
                    if ($installMode -match "AppInsights") {
                        if ($applicationInsights -ne $null) {
                            $wizdomConfig.Analytics.AnalyticsEnabled.'#text' = 'true'
                            $wizdomConfig.Analytics.SubscriptionID = $config.Azure.SubscriptionId
                            $wizdomConfig.Analytics.ResourceGroup = $config.Azure.ResourceGroupName
                            $wizdomConfig.Analytics.AIInstanceName = $applicationInsightsName
                        }
                    }
                    if ($environment -eq 'Modern' -or $environment -eq 'Both' ) {
                        $wizdomConfig.Modern.OptInToModernDeploy.'#text' = 'true'
                    }
                    Publish-WizdomConfig -WizdomUrl $websiteUrl -configObject $wizdomConfig -accessToken $accessToken |Write-Verbose

                    Set-WizdomdataStore -WizdomUrl $websiteUrl -dataStoreUrl $wizdomDataSiteSPO -dataStoreOwner $config.Office365.WizdomSearchUser.Account -accessToken $accessToken |Write-Verbose

                    #Setting Wizdom default language to $config.Wizdom.defaultLocalID to reduce translation file size
                    $wizdomAllLanguages = Get-WizdomLanguages -WizdomUrl $websiteUrl -accessToken $accessToken
                    Set-WizdomLanguage -WizdomUrl $websiteUrl -accessToken $accessToken -culture ($wizdomAllLanguages.where({$_.lcid -eq $config.Wizdom.defaultLocaleID}).tag)|Write-Verbose
                    
                    #Creating default links in PowerPanel
                    $LinkCollection = Get-WizdomLinkCollection -WizdomUrl $websiteUrl -accessToken $accessToken -LinkCollectionGuid $WizdomConfig.Servicemenu.Items.ViewId
                    $wizdomPowerPanelCategory = Create-WizdomLinkCollectionCategory -WizdomUrl $websiteUrl -accessToken $accessToken -Name "Wizdom" -LinkCollectionGuid $WizdomConfig.Servicemenu.Items.ViewId
                    $CorporateLinks = Get-WizdomCorporateLinks -WizdomUrl $websiteUrl -accessToken $accessToken
                    $result = Add-WizdomLinkCorporateLinkToLinkCollection -WizdomUrl $websiteUrl -accessToken $accessToken -LinkCollectionGuid $WizdomConfig.Servicemenu.Items.ViewId -JsonObject ($CorporateLinks.links.where({$_.id -eq 2})|convertto-json -Depth 3) -categoryId $wizdomPowerPanelCategory.id
                    $result = Add-WizdomLinkCorporateLinkToLinkCollection -WizdomUrl $websiteUrl -accessToken $accessToken -LinkCollectionGuid $WizdomConfig.Servicemenu.Items.ViewId -JsonObject ($CorporateLinks.links.where({$_.id -eq 3})|convertto-json -Depth 3) -categoryId $wizdomPowerPanelCategory.id
                    #$LinkCollection = Get-WizdomLinkCollection -WizdomUrl $websiteUrl -accessToken $accessToken -LinkCollectionGuid $WizdomConfig.Servicemenu.Items.ViewId
                    #$LinkCollection.categories[1].links = $LinkCollection.categories.where({$_.name -eq "Uncategorized"}).links
                    #$LinkCollection.categories[0].links = '[]'
                    #$result = Order-WizdomLinkCollection -WizdomUrl $websiteUrl -accessToken $accessToken -LinkCollectionGuid $WizdomConfig.Servicemenu.Items.ViewId -JsonObject ($LinkCollection.categories |convertto-json -Depth 3 -Compress)
                } catch {
                    throw $_
                }
            }
        } else {
            $licenseResponse = "Continuing without PIN" 
        }
        Write-WizdomHost -messageType OK -outputMaxLength $outputMaxLength -initialStringLength $output -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    } catch {
        $licenseResponse = "Continuing without PIN" 
    } finally {
        if ($licenseResponse -ne "Got updated license") {
            if ($wizdomPIN -ne "EXIT") {
                Write-WizdomHost -messageType ERROR -outputMaxLength $outputMaxLength -initialStringLength $output -afterOutputMessage $_.Exception.Message -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
            }
            Write-Host "Manual configuration needed:"
            if ($environment -eq 'Modern' -or $environment -eq 'Both') {
                Write-Host "  Configure Wizdom on Modern in Wizdom Config Center / Admin / Modern Experiences:"
                Write-Host "  (URL: $($websiteUrl + "/Base/Pages/Configuration.aspx?SPHostUrl=" + $SharePointUrl))"
                Write-Host "    Click 'Associate modern packages with this Wizdom instance'"
                Write-Host "    Click 'Deploy modern packages immediately'"
                Write-Host "    Set a checkmark in 'Selecting this option...'"
                Write-Host "    Click 'Save configuration'"
            }
            Write-Host "  Datastore configuration in Wizdom Config Center / Admin / SiteCollections:"
            Write-Host ("    Url: " + $wizdomDataSiteSPO)
            Write-Host ("    User: " + $wizdomSearchUser.SignInName)
            Write-host "  Select a language in Wizdom Config Center / Admin / Languages. If no language is selected, all translations will be loaded by default (performance)"
            if ($installMode -match "AppInsights") {
                Write-Host "  Wizdom Analytics in Wizdom Config Center / Admin / Analytics:"
                Write-Host "    Click 'Enable analytics and statistics collection'"
                Write-Host "    Subscription ID: $($config.Azure.SubscriptionId)"
                Write-Host "    Resource Group: $($config.Azure.ResourceGroupName)"
                Write-Host "    Application Insights instance name: $($applicationInsightsName)"
            }
        }
    }
#endregion

#region Mobile Installation
    #todo If removemode = Azuresomething and installmode = None så ender den stadig med at forsøge at køre mobileinstall - er det ok?
    #skal mobileinstall køres? hvad nu hvis der ikke var en mobile installation før? removeMode includeMobile?
    #if ($installMode -match "MobileSolution" -or ($removeMode -match "SharePoint" -or $removeMode -match "Azure")) {
    if ($installMode -match "MobileSolution") {
#        if ($WizdomSPApp -eq $null -and -not (($removeMode -match "SharePoint" -or $removeMode -match "Azure") -and $installMode -eq "NoneTestConfig")) {
#            $wizdomSharePointAppId = (Get-PnPApp -Connection $pnpSharePointAdminAppContext).where({$_.Title -eq $config.SharePointClassic.WizdomApp.Title}).id
#        } else {
            $wizdomSharePointAppId = $WizdomSPApp.Id
#        }

        #Add to output fil
        $outFileArrayMobileApp += New-Object PSObject -Property @{
        Line = 1
        Field = "MobileAppURL"
        Input = $websiteUrlMobile}
        
        if ($UseWebLoginSharePoint.IsPresent) {
            $mobileParameters = @{
                mobileModuleName = $config.MobileApp.Wizdom.ModuleName
                wizdomMobileSiteSPO = ($SharePointUrl + "/sites/" + $config.MobileApp.SharePoint.Url)
                wizdomWebsiteUrl = $websiteUrl
                sharePointAdminUsr = $SharePointTenantAdmin
                mobileSiteCollectionOwner = $config.MobileApp.SharePoint.Owner
                mobileSiteCollectionTitle = $config.MobileApp.SharePoint.Title
                mobileSiteCollectionLocaleId = $config.MobileApp.SharePoint.LocaleId
                wizdomSharePointAppId = $wizdomSharePointAppId
                azureResourceGroupName = $config.Azure.ResourceGroupName
                azureMobileWebAppUrl = $websiteUrlMobile
                mobileModuleZip = $PSScriptRoot + $config.Wizdom.mobileModuleZip
                outputMaxLength = $outputMaxLength
                lightningPageTemplate = $PSScriptRoot + $config.Wizdom.lightningPageTemplate
                installMode = $installMode
                removeMode = $removeMode
                wizdomSharePointAppName = $config.SharePointClassic.WizdomApp.Title
            }					  
        } else {
            $mobileParameters = @{
                mobileModuleName = $config.MobileApp.Wizdom.ModuleName
                wizdomMobileSiteSPO = ($SharePointUrl + "/sites/" + $config.MobileApp.SharePoint.Url)
                wizdomWebsiteUrl = $websiteUrl
                sharePointAdminUsr = $SharePointTenantAdmin
                sharePointAdminCredentials = $credentialsOffice365Admin
                mobileSiteCollectionOwner = $config.MobileApp.SharePoint.Owner
                mobileSiteCollectionTitle = $config.MobileApp.SharePoint.Title
                mobileSiteCollectionLocaleId = $config.MobileApp.SharePoint.LocaleId
                wizdomSharePointAppId = $wizdomSharePointAppId
                azureResourceGroupName = $config.Azure.ResourceGroupName
                azureMobileWebAppUrl = $websiteUrlMobile
                mobileModuleZip = $PSScriptRoot + $config.Wizdom.mobileModuleZip
                outputMaxLength = $outputMaxLength
                lightningPageTemplate = $PSScriptRoot + $config.Wizdom.lightningPageTemplate
                installMode = $installMode
                removeMode = $removeMode
                wizdomSharePointAppName = $config.SharePointClassic.WizdomApp.Title
            }
        }

        Install-WizdomMobile @mobileParameters -WhatIf:$WhatIfPreference -Verbose:$VerbosePreference
    }

    Write-Host "Recommended search configuration on a tenant where Wizdom is setup for the first time. It is not required by the product:"
    Write-Host "- Set RefinableDate00 to map People:SPS-Birthday, Alias: w365Birthday"
    Write-Host "- Set RefinableDate01 to map People:SPS-HireDate, Alias: w365HireDate"
    Write-Host "- Set RefinableDate02 to map ows_q_DATE_W365_RevisionDate, Alias: w365RevisionDate"
    Write-Host "- Set RefinableDate04 to map ows_W365_NewsDate, Alias: W365NewsDate"
    Write-Host "- Set RefinableString03 to map to ows_W365_RelatedTopic, Alias: RelatedTopic" 
    Write-Host "- Set RefinableString04 to map to ows_PublishingContact, ows_W65_DepartmentContact, Alias: Contact" 
    Write-Host "- Set RefinableString06 to map to ows_W365_Language, ows_q_TEXT_TranslationLanguage? , Alias: Language" 
    Write-Host "- Set RefinableString07 to map to ows_Department, ows_NewsDepartment, ows_W365_Departments, Alias: Department" 
    Write-Host "- Set RefinableString08 to map to ows_Location, ows_NewsLocation, ows_W365_Locations, ows_W365_ManualLocation, ows_q_TEXT_Location, Alias: Location"

    Disconnect-PnPOnline
    Disconnect-AzureRmAccount -Username $SharePointTenantAdmin | Out-Null
    Disconnect-AzureRmAccount -Username $o365Context.Context.Account.Id | Out-Null
    Disconnect-AzureAD
    Disconnect-SPOService

    if ($PSCmdlet.ShouldProcess("SharePointOnlineInstallationFunctions", "Remove-Module")) {
        remove-module SharePointOnlineInstallationFunctions -Force
    }

#endregion

#region export Settings as File
    if ($outputSettingsFile -ne "") {
        $outFileArrayBasic += New-Object PSObject -Property @{
        Line = 16
        Field = "Wizdom Admin Account"
        Input = ""}

        $outFileArrayCourseManagement+= New-Object PSObject -Property @{
        Line = 1
        Field = "SmtpHost"
        Input = ""}
        $outFileArrayCourseManagement += New-Object PSObject -Property @{
        Line = 2
        Field = "SmtpPort"
        Input = ""}
        $outFileArrayCourseManagement += New-Object PSObject -Property @{
        Line = 3
        Field = "SmtpUserName"
        Input = ""}
        $outFileArrayCourseManagement+= New-Object PSObject -Property @{
        Line = 4
        Field = "SmtpUserPwd"
        Input = ""}
        $outFileArrayCourseManagement += New-Object PSObject -Property @{
        Line = 5
        Field = "SenderEmail"
        Input = ""}
        $outFileArrayCourseManagement += New-Object PSObject -Property @{
        Line = 6
        Field = "SenderName"
        Input = ""}

        if ($PSCmdlet.ShouldProcess("OutputCsvFile", "Create")) {
            $outFileArrayBasic | sort Line | select Field, Input |Export-Csv -Path $outputSettingsFile -NoTypeInformation -Force
            if ($outFileArrayConnectionStrings.Count -gt 0) {
                $outFileArrayConnectionStrings | sort Line | select Field, Input | ConvertTo-Csv -NoTypeInformation | select -Skip 1 | Add-Content -Path $outputSettingsFile
            }
            if ($outFileArrayCourseManagement.Count -gt 0) {
                Add-Content -Path $outputSettingsFile -Value """Below only used for course management licenses"","""""
                $outFileArrayCourseManagement | sort Line | select Field, Input | ConvertTo-Csv -NoTypeInformation | select -Skip 1 | Add-Content -Path $outputSettingsFile
            }
            if ($outFileArrayMobileApp.Count -gt 0) {
                Add-Content -Path $outputSettingsFile -Value """Below only used for MobileApp installation"","""""
                $outFileArrayMobileApp | sort Line | select Field, Input | ConvertTo-Csv -NoTypeInformation | select -Skip 1 | Add-Content -Path $outputSettingsFile
            }
            if ($outFileArrayAnalytics.Count -gt 0) {
                Add-Content -Path $outputSettingsFile -Value """Below only used for Wizdom Analytics"","""""
                $outFileArrayAnalytics | sort Line | select Field, Input | ConvertTo-Csv -NoTypeInformation | select -Skip 1 | Add-Content -Path $outputSettingsFile
            }
        }
    }

#endregion
#2Do:
#Check tenant site customization flags set? - warning to user 24 hours!
#test for modern deployment opt-in on tenant - are there already one?

## Include timerjob to alert Admin on expiring Client/Secrets
## Check if People / Departments & Location termset exists and available for tagging
## Change app trust to local site trust

#Optimize speed - start / no wait / IS ready?

#Tests before release
#1) -deleteExistingSites -skipInstall
#2) -includeMobileSolution skipCheckPrerequisites
#3) -includeMobileSolution skipCheckPrerequisites -skipApplicationInsights MFA -deleteExistingInstallation

Write-Host ("Installation endtime: " + (get-date)) -ForegroundColor White
