<# TPDT-268 Finalize the database setup after staging a new pharmacy This script re-implements the autoConfigPharmacy of the DBA in an automated way with interactions with a keyvault to fetch secrets and certificate TSC 08.02.2024 Creation #> # param ( # #[Parameter(Position=0,mandatory=$true)] # [string] $SqlInstance ="SSUNB008VM01\APSSQL" # ) #if ran from TSC computer, execute on sun008, otherwise execute locally if($env:COMPUTERNAME -ne "CGAL41556"){ $SqlInstance = "$($env:COMPUTERNAME)\APSSQL" } else{ $SqlInstance ="SSUNB008VM01\APSSQL" } if (test-path "C:\Program Files\PackageManagement\Az.Accounts") {Import-Module "C:\Program Files\PackageManagement\Az.Accounts"}else{exit 42101} if (test-path "C:\Program Files\PackageManagement\Az.keyvault") {Import-Module "C:\Program Files\PackageManagement\Az.keyvault"}else{exit 42101} if (test-path "C:\Program Files\PackageManagement\Az.compute") {Import-Module "C:\Program Files\PackageManagement\Az.compute"}else{exit 42101} #Import-Module SqlServer <# .SYNOPSIS Executes a T-SQL script on a SQL Server database without trying to parse variables. .DESCRIPTION This function executes a T-SQL script on a specified SQL Server database. The script is split into batches based on the GO statements and each batch is executed separately. SQLCMD type variables like $(ESCAPE_SQUOTE(WMI(DatabaseName))) are NOT parsed and do not cause errors if they are present in dynamic sql .PARAMETER Server The name of the SQL Server instance to connect to. .PARAMETER Database The name of the database on the SQL Server instance to execute the script against. .PARAMETER ScriptPath The file path of the T-SQL script to execute. .EXAMPLE Invoke-SqlScript -Server "YourServer" -Database "YourDatabase" -ScriptPath "YourScript.sql" Executes the T-SQL script "YourScript.sql" on the "YourDatabase" database of the "YourServer" SQL Server instance. .NOTES Author: Thierry Schork Date: 2024-02-13 #> function Invoke-SqlScript { param( [string]$Server, [string]$Database, [string]$ScriptPath, [string]$script ) # Load the assembly containing the SqlCommand class Add-Type -AssemblyName System.Data # Create a connection string $connectionString = "Server=$Server;Database=$Database;Integrated Security=True" # Create a SqlConnection object $connection = New-Object System.Data.SqlClient.SqlConnection($connectionString) # Open the connection $connection.Open() # Read the content of the script file if('' -ne $ScriptPath){ $scriptContent = Get-Content -Path $ScriptPath -Raw } if('' -ne $script){ $scriptContent = $script } # Split the script into batches based on the GO statements $scriptBatches = $scriptContent -split [regex]'(?smi)^[\s]*GO[\s]*$' | Where-Object { $_.Trim() -ne '' } # Create a SqlCommand object $command = $connection.CreateCommand() foreach ($batch in $scriptBatches) { #$batch = $batch.Trim() #$batch = $batch -replace '(?m)^GO\s*', '' if ($batch.ToLower() -ne "go"){ try { # Set the command text for the batch $command.CommandText = $batch # Execute the command $null = $command.ExecuteNonQuery() } catch { Write-Error "Error in the file $fileShort" Write-Error $_.Exception.Message Write-Error "-------------------" Write-Error $batch } } } # Close the connection $connection.Close() } $ErrorActionPreference = "Stop" $DebugPreference = 'SilentlyContinue' #overrides #$ErrorActionPreference = "continue" #$DebugPreference = 'Continue' $rootFS = Get-Location #do we want to execute jobs from TiaPharm. No, because the package #should have deployed them just before this script $exec_triapharm = $false # Replace these values with your Azure AD service principal details $clientId = "65d289fd-4bea-464f-a1cd-417f74d26888" #pcpl-ApoLsPoc-setupDataEncryptionwithKeyVault $clientSecret = "dPQ8Q~N5btnUb7cj~H4YZaDDQo7kJNZlpxbRua2a" $tenantId = "7844775a-a9cc-4c33-a5ae-36dcf6660f45" #galenica # Replace these values with your Azure Key Vault details #$resourceGroupName = "rg-ApoLsPoc-Keyvault" $keyVaultName = "kv-ApoLsPoc-Tde" #secret to fetch from the key vault $secretFtpPassword ="centralinfra---svc-APH-trans" $secretMasterKey ="MasterKey" $secretTriaPharmSQLCert = "TriaPharmSQLCert" # Convert the client secret to a secure string $securePassword = ConvertTo-SecureString -String $clientSecret -AsPlainText -Force # Create a PSCredential object $psCred = New-Object System.Management.Automation.PSCredential -ArgumentList $clientId, $securePassword # Authenticate using the service principal $null = Connect-AzAccount -ServicePrincipal -Credential $psCred -TenantId $tenantId # Get the secret from Azure Key Vault # $secret = Get-AzKeyVaultSecret -VaultName $keyVaultName -Name $secretName # write-host "domain: $($ua.Tags["domain"])" # Display the secret value #Write-Host "Secret Value: $($secret.SecretValue | ConvertFrom-SecureString -AsPlainText)" ##region derive alias from hostname $hostname = $SqlInstance $position = $hostname.IndexOf("\") if ($position -gt 0){ #remove instance name $hostname = $hostname.SubString(0,$position) } $ou = $null $customer = $null $clientpath = $null $format = 'GCM' if ($hostname.IndexOf("NB") -gt 0){ #old hostname, like SSUNB661VM01 $ou = $hostname.Substring(5,3) if($hostname -like "SSUNB*"){ $customer="SUN" } if($hostname -like "SAMNB*"){ $customer="AMA" } if($hostname -like "SCVNB*"){ $customer="CVI" } } else{ #new$customer, like SWAMA701VM23 $ou = $hostname.Substring(5,3) if($hostname -like "SWSUN*"){ $customer="SUN" } if($hostname -like "SWAMA*"){ $customer="AMA" } if($hostname -like "SWCVI*"){ $customer="CVI" } } #used to map jobs scripts subfolders when executing scripts with customer abbreviation switch ($Customer){ 'AMA'{ $clientpath = 'AMAVITA' } 'SUN'{ $clientpath = 'SUNSTORE' } 'CVI'{ $clientpath = 'CVI' } } if($null -eq $format){ Write-Error "Format could not be derived from hostname $hostname" } if($null -eq $customer){ Write-Error "Customer could not be derived from hostname $hostname" } if ($null -eq $ou){ Write-Error "OU could not be derived from hostname $hostname" } $alias = "$($customer)$($ou)APS" #region Server DEV/TEST/PROD $PharmacyShortName = $customer + $ou $PharmaciesDEV = "SUN007","SUN008","CVI506","AMA704" $PharmaciesVALI = "SUN004","SUN006","CVI503","CVI504","AMA705","AMA707","AMA888","CVI888","SUN888" $PharmaciesACCE = "SUN001","SUN002","CVI501","CVI502","AMA701","AMA702","AMA703","CVI505","SUN003" $PharmaciesTRAINING = "SUN011","CVI507","CVI508","AMA988","AMA989" if ($PharmaciesDEV -match $PharmacyShortName) { $ContextType = 'DEVE' $CentraleServer = "SWINTDB01" } elseif ($PharmaciesVALI -match $PharmacyShortName) { $ContextType = 'VALI' $CentraleServer = "SWTSTDB01" } elseif ($PharmaciesACCE -match $PharmacyShortName) { $ContextType = 'ACCE' $CentraleServer = "SWPRDDB01" } elseif ($PharmaciesTRAINING -match $PharmacyShortName) { $ContextType = 'FORM' $CentraleServer = "SWPRDDB01" } else { $ContextType = 'PROD' $CentraleServer = "SWPRDDB01" } #endregion #endregion #define a db credential, for testing without hte service principal #$DBpassword = Get-AzKeyVaultSecret -VaultName $keyVaultName -Name "ua208700" #$dbCred = New-Object System.Management.Automation.PSCredential -ArgumentList 'ua208700@centralinfra.net', $DBpassword.SecretValue #$dbCred = Get-SqlCredential -Name "centralinfra\ua208700" -Identity "centralinfra\ua208700" -Secret $DBpassword.SecretValue ##Check if instance is reachable $instanceReachable=$false try { $null = Invoke-Sqlcmd -ServerInstance $SqlInstance -Query "select 1" -QueryTimeout 5 -Database master $instanceReachable=$true Write-Debug "Connection to the SQL instance ok" } catch { Write-Error "SQL instance '$($SqlInstance)' is not reachable or the principal doesn't have permissions to connect" } ##region check out triaOne release $checkoutGit = $false if($true -eq $checkoutGit){ $FolderPath = "triaOne_jobs" if (Test-Path -Path $FolderPath) { # If the folder exists, remove its contents Get-ChildItem -Path $FolderPath | Remove-Item -Force -Recurse Write-Host "Cleaned existing folder: $FolderPath" } else { # If the folder doesn't exist, create it New-Item -ItemType Directory -Path $FolderPath | Out-Null Write-Host "Created new folder: $FolderPath" } # Define the URL of the Git repository and the subfolder you want to check out $repositoryUrl = "http://shcnbtfs01.hcisolutions.ch:8080/tfs/TriaPharmCollection/_git/TriaOne" $subfolderPath = "SQL\Jobs" $localRepo = "triaOne_repo" # Clone the Git repository without fetching files on the selected branch git clone -n -b rel/23.6 $repositoryUrl $localRepo # Move to the cloned repository directory Set-Location $localRepo git checkout HEAD $subfolderPath git pull # Move the subfolder to the desired location $destination = Join-Path -Path $rootFS -ChildPath $FolderPath Move-Item -Path $subfolderPath -Destination $destination -Force -Recurse Set-Location $rootFS #Remove-Item $localRepo -Force } #endregion #$instanceReachable = $false if($true -eq $instanceReachable){ ##fetch OU details from control center ## CLA: can be ignored due to SWITCH directly after this QUERY <# $query=@" SELECT LOWER([SERV].[SE_DNS]) AS SE_DNS, CASE WHEN UPPER(LEFT([SERV].[SE_DNS], 3)) = 'SCV' THEN 'CVI' WHEN UPPER(LEFT([SERV].[SE_DNS], 3)) = 'SSU' THEN 'SUN' WHEN UPPER(LEFT([SERV].[SE_DNS], 3)) = 'SAM' THEN 'AMA' ELSE UPPER(LEFT([SERV].[SE_DNS], 3))END AS [customer], CASE WHEN UPPER([SERV].[SE_designation]) LIKE '%ACCEPTANCE%' THEN 'ACCE' WHEN UPPER([SERV].[SE_designation]) LIKE '%FORMATION%' THEN 'FORM' WHEN UPPER([SERV].[SE_designation]) LIKE '%SCHULUNG%' THEN 'FORM' ELSE 'PROD' END AS [context], LEFT(LTRIM([cfg].[CF_value]), 4) AS [version], LOWER([SERV].[SE_DNS] + '\' + [SERV].[SE_instance_name]) AS [aliasInstance] FROM [ControlCenter].[dbo].[Server] SERV JOIN [ControlCenter].[dbo].[Config] cfg ON [cfg].[CF_server] = [SERV].[SE_id] JOIN [ControlCenter].[dbo].[ConfigType] cfgt ON [cfgt].[CFTY_id] = [cfg].[CF_configtype] JOIN [ControlCenter].[dbo].[Entity] ent ON [ent].[EN_id] = [SERV].[SE_entity] WHERE [cfgt].[CFTY_type] = 'TriaPharmVersion' AND [SERV].[SE_status] = 1 AND ( ([SERV].[SE_end_prod_date] > GETDATE()) OR ([SERV].[SE_end_prod_date] IS NULL)) AND [SERV].[SE_designation] NOT LIKE '%Fermé%' AND [SERV].[SE_designation] NOT LIKE '%REF%' AND [ent].[EN_code] IN ( 'AMA', 'SUN', 'CVI' ) AND LOWER([SERV].[SE_DNS]) LIKE '$alias%' ORDER BY [SERV].[SE_entity], SE_DNS; "@ #> # $dbacc = Invoke-Sqlcmd -ServerInstance hcimon -Database ControlCenter -Query $query if($null -eq $dbacc){ Write-Output "No results in HCIMON, faking them" $dbacc = New-Object psobject #$dbacc | Add-Member -MemberType NoteProperty -Name "context" -Value "DEVE" $dbacc | Add-Member -MemberType NoteProperty -Name "context" -Value $ContextType $dbacc | Add-Member -MemberType NoteProperty -Name "customer" -Value $customer $dbacc | Add-Member -MemberType NoteProperty -Name "version" -Value -1 $dbacc | Add-Member -MemberType NoteProperty -Name "SE_DNS" -Value $hostname $dbacc | Add-Member -MemberType NoteProperty -Name "aliasInstance" -Value $sqlVariables } ##step 1: configure SQL instance max memory Invoke-Sqlcmd -ServerInstance $SqlInstance -Database master -InputFile .\1_Configure_InstanceMemory.sql Write-Output "Executed 1_Configure_InstanceMemory.sql" ##step 2: set db identities [string]$query = Get-Content -Path .\2_Set_Identity.sql $query = $query.Replace("`$format", $format) $query = $query.Replace("`$fqdn", $alias) $query = $query.Replace("`$customer", $customer) $query = $query.Replace("`$context", $dbacc.context) # ['context']) Invoke-Sqlcmd -ServerInstance $SqlInstance -Database master -Query $query Write-Output "Executed 2_Set_Identity.sql" ##step 3: activate clr Invoke-Sqlcmd -ServerInstance $SqlInstance -Database master -InputFile .\3_Execute_CLR.sql Write-Output "Executed 3_Execute_CLR.sql" ##step 4: map sql users Invoke-Sqlcmd -ServerInstance $SqlInstance -Database master -InputFile .\6_Map_SQLUsers.sql Write-Output "Executed 6_Map_SQLUsers.sql" ##step 4: remove orphaned users Invoke-Sqlcmd -ServerInstance $SqlInstance -Database master -InputFile .\8_Remove_OrphanedSQLUsers.sql $orphans | Format-Table Write-Output "Executed 8_Remove_OrphanedSQLUsers.sql" ##step 5: FTP password modification on central server $ftpPassword = Get-AzKeyVaultSecret -VaultName $keyVaultName -Name $secretFtpPassword $query = Get-Content -Path .\9_Update_FTPLogin_Central.sql $query = $query.Replace("`$ftppass", $($ftpPassword.SecretValue | ConvertFrom-SecureString )) ####################################### -AsPlainText)) $query = $query.Replace("`$fqdn", $alias) #Invoke-Sqlcmd -ServerInstance gcmPrdCent -Database arizona -Query $query Invoke-Sqlcmd -ServerInstance $CentraleServer -Database arizona -Query $query Write-Output "Executed 9_Update_FTPLogin_Central.sql" ##step 6: execute all DBA packages $LogFile = "C:\Temp\DBApackagesCLA.log" try{ Get-ChildItem -path .\dba_packages -Filter *.sql | ForEach-Object { $file = $_ $fileShort = Split-Path -Path $file -Leaf Invoke-SqlScript -Server $SqlInstance -Database master -ScriptPath $file.FullName Write-Output "executed DBA package file $fileShort" } } catch { $file | out-file -FilePath $LogFile -Append -Encoding utf8 $_ | out-file -FilePath $LogFile -Append -Encoding utf8 } ##step 7: execute all DBA structure scripts Get-ChildItem -path .\dba_structure -Filter *.sql | ForEach-Object { $file = $_ $fileShort = Split-Path -Path $file -Leaf Invoke-SqlScript -Server $SqlInstance -Database master -ScriptPath $file.FullName Write-Output "executed DBA structure file $fileShort" } ##step 8: execute all DBA stored procedures scripts Get-ChildItem -path .\dba_storedProcedures -Filter *.sql | ForEach-Object { $file = $_ $fileShort = Split-Path -Path $file -Leaf Invoke-SqlScript -Server $SqlInstance -Database master -ScriptPath $file.FullName Write-Output "executed DBA stored procedure file $fileShort" } ##region triaOne jobs if($true -eq $exec_triapharm){ ##step 9: execute triaOne pharmacy jobs scripts Get-ChildItem -path .\cp_triaOne_jobs\pharmacy -Filter *.sql | ForEach-Object { $file = $_ $fileShort = Split-Path -Path $file -Leaf Invoke-SqlScript -Server $SqlInstance -Database master -ScriptPath $file.FullName Write-Output "executed triaOne pharmacy job file $fileShort" } ##step 10: execute triaOne pharmacy customer jobs scripts Get-ChildItem -path .\cp_triaOne_jobs\pharmacy\$clientpath -Filter *.sql | ForEach-Object { $file = $_ $fileShort = Split-Path -Path $file -Leaf Invoke-SqlScript -Server $SqlInstance -Database master -ScriptPath $file.FullName Write-Output "executed triaOne pharmacy customer job file $fileShort" } } #endregion ##region DBA jobs ##step 11: execute all DBA global jobs scripts Get-ChildItem -path .\dba_jobs\ -Filter *.sql | Where-Object {$_.Name -NotMatch "several"} | ForEach-Object { $file = $_ $fileShort = Split-Path -Path $file -Leaf Invoke-SqlScript -Server $SqlInstance -Database master -ScriptPath $file.FullName Write-Output "executed DBA global job file $fileShort" } ##step 12: execute all DBA pharmacy jobs scripts Get-ChildItem -path .\dba_jobs\Pharmacy -Filter *.sql | ForEach-Object { $file = $_ $fileShort = Split-Path -Path $file -Leaf Invoke-SqlScript -Server $SqlInstance -Database master -ScriptPath $file.FullName Write-Output "executed DBA pharmacy job file $fileShort" } ##step 13: execute all DBA customer specific pharmacy jobs scripts Get-ChildItem -path .\dba_jobs\Pharmacy\$clientpath -Filter *.sql | ForEach-Object { $file = $_ $fileShort = Split-Path -Path $file -Leaf Invoke-SqlScript -Server $SqlInstance -Database master -ScriptPath $file.FullName Write-Output "executed DBA pharmacy $clientpath job file $fileShort" } #endregion ##step 14: execute all DBA alerts scripts Get-ChildItem -path .\dba_alerts -Filter *.sql | ForEach-Object { $file = $_ $fileShort = Split-Path -Path $file -Leaf Invoke-SqlScript -Server $SqlInstance -Database master -ScriptPath $file.FullName Write-Output "executed DBA alert file $fileShort" } ##step 15: execute schedule shuffle script $null = Invoke-Sqlcmd -ServerInstance $SqlInstance -Database master -InputFile .\10_Change_Schedules_Subscriptions.sql Write-Output "Executed 10_Change_Schedules_Subscriptions.sql" ##step 16: execute schedule shuffle script $null = Invoke-Sqlcmd -ServerInstance $SqlInstance -Database master -InputFile .\11_Run_Job_CheckReplications.sql Write-Output "Executed 11_Run_Job_CheckReplications.sql" ##step 17: Atlas configuration $masterKey = Get-AzKeyVaultSecret -VaultName $keyVaultName -Name $secretMasterKey $triapharmSqlCert = Get-AzKeyVaultSecret -VaultName $keyVaultName -Name $secretTriaPharmSQLCert $bckcert = 'D:\SQLDatabaseDump\Certificates\MK\'+$alias.Substring(0,6).toupper()+'_master.bak' [string]$query = Get-Content -Path .\12_Broker_Config.sql $query = $query.Replace("`$mk", $($masterKey.SecretValue | ConvertFrom-SecureString )) # -AsPlainText)) $query = $query.Replace("`$cert", $($triapharmSqlCert.SecretValue | ConvertFrom-SecureString )) # -AsPlainText)) $query = $query.Replace("`$bck", $bckcert) Invoke-Sqlcmd -ServerInstance $SqlInstance -Database arizona -Query $query Write-Output "Executed 12_Broker_Config.sql" ##step 18: create ad logins Invoke-Sqlcmd -ServerInstance $SqlInstance -Database master -InputFile .\13_Create_AD_Logins.sql Write-Output "Executed 13_Create_AD_Logins.sql" ##step 19: final check on pharmacy #removed because does not brings any value #in an automated run, as this requires a human to verify and act #Invoke-Sqlcmd -ServerInstance $SqlInstance -Database master -InputFile '.\Check Instance Pharmacy.sql' #Write-Output "Executed Check Instance Pharmacy.sql" ##step 20: update system_site on the central $fqdn = [System.Net.Dns]::GetHostByName($env:computerName) #determine the OU id of the current pharmacy (ran locally) $query =@" USE [Arizona] DECLARE @ou_id INT EXEC Arizona.dbo.sp_bmc_Bmc_Applic_Default @in_job_type = 3, @in_param_int_1 = null, /* Company */ @in_param_int_2 = null, /* Subsidiary */ @in_param_varchar_1 = 'cvCurrentOrganizationalUnit', @out_default_value = @ou_id output, @out_param_int_1 = null SELECT @ou_id as ou_id "@ $local_ou = Invoke-Sqlcmd -ServerInstance $SqlInstance -Database arizona -Query $query #update the centrale $query=Get-Content -Path .\20_update_system_site_centrale.sql $query = $query.Replace("`$ou_id", $local_ou.ou_id) $query = $query.Replace("`$fqdn", $fqdn.HostName) $query = $query.Replace("`$alias", $alias) Invoke-Sqlcmd -ServerInstance gcmPrdCent -Database arizona -Query $query Write-Output "Executed 20_update_system_site_centrale.sql" ##step 21: update Point_of_sale on the central #fetch a list of mac adresses $macs = @{} $pos = 0 Get-NetAdapter | Select-Object ifIndex, Name, MacAddress | ForEach-Object { $macs.Add($pos, $_.MacAddress) $pos++ } if($macs.Count -gt 1){ #we have several mac addresses, unable to define which one should be used in point_of_sale Write-Error "Several mac addresses are active, no clue which one to save in Point_of_sale" } else{ $query=Get-Content -Path .\21_update_point_of_sale_centrale.sql $query = $query.Replace("`$ou_id", $local_ou.ou_id) $query = $query.Replace("`$fqdn", $fqdn.HostName) $query = $query.Replace("`$mac", $macs[0].value) Invoke-Sqlcmd -ServerInstance gcmPrdCent -Database arizona -Query $query Write-Output "Executed 21_update_point_of_sale_centrale.sql" } }