Menu Close

Moving app GUIDs across Qlik Environments

App GUIDs in Qlik Sense are unique for each environment. Different environment, different GUID. Different URL for the app. That is life in Qlik Sense. This setup, while frustrating for developers, generally is a non-issue that we accept and accommodate in our design. However, this limitation can become challenging or even an obstacle in several instances:

  1. There is a custom app navigation bar (bypassing the Qlik Sense Hub & App Overview)
  2. URL links in a chart/object in one app to navigate a sheet in a different app
  3. ETL processes that may be centralized/optimized by having a model app & binary load into one or several Qlik Sense apps (this is a pain-point for those of us who are accustomed to QlikView development)
  4. Testers/Quality control department pushes back against the URL differences beyond the domain changing from Dev / Test / Prod environments
  5. The developer in charge of the Qlik environments (or deployment) is OCD & cannot stand having differences on the Qlik front end

The first two use cases can be bypassed without issue with conscientious design (that is why we are paid, after all). Writing a customized extension & referencing the Qlik API to populate the custom sidebar is a solution, and setting the app ID as a variable using Peek() and the app list from the Qlik Postgres database can solve the second. However, the third case (binary load) is where those innovations come to a complete stop.

Binary [Lib://AppRepository/cc750977-e01c-4c4a-8112-b3ce98b420db];

What makes this case different? It has to do with the Binary command in Qlik and the restrictions around using it. The binary load must be the first executed command in the Load Script. I am not sure what the reason for that is; my theory is that the binary load executes a Copy command and replaces sections of the app running the script, and so to avoid catastrophe Qlik added restrictions around the command. The Binary load was much more useful in QlikView since a failed load in the front-end app would drop triggers and generally wreak havoc on the developed app (I do not recall what else, but there were several items and could set a developer back several hours of work). I have found binary loads useful in Qlik Sense when sharing data models between apps or needing to refresh apps with hourly data but also refreshing the historic data nightly (which could take 45+ minutes to load). Qlik Sense does not support an easy method to execute a partial reload in the QMC, and the binary load method helps keep the task management in the Qlik QMC. Binaries can also be made to QlikView models, so do not break up / duplicate the data pipeline if there is a dual environment in production.

But how is a binary load referenced? The name of apps in Qlik Sense are stored as GUIDs, so the binary must reference the GUID of the target app. Since it must be the first executed command in the load script, there is no opportunity to set a variable to the GUID of the app. There are two solutions: either change the GUID in the binary load command of the load script when moving it to the test/production environment (automation of this process would be preferred) or move apps into the new environment and assign the GUID from the previous environment as the GUID in the new one.

Another reason to move the GUID (and one most of us find ourselves in) is that there is a requirement of having the production environment match the development environment as much as possible. URL in production matching the URL in development with an exception for the domain can be a sticking point in your SDLC.

OK, let’s say one or two of those points above convinced me. Now how can I migrate the GUIDs in Qlik Sense?

I was convinced they could be because that is the whole reason the GUID was invented in the first place: to make DB mergers possible without key collision. If you have poked around enough on the Qlik Sense server, the first spot that comes to mind is the PostGres database that Qlik uses to save all of its information about various apps. After reading dozens of blogs & Qlik Help files that cautioned emphatically against tampering with the database as it is always unsupported & could hopelessly corrupt it, I moved away from pgAdmin & started studying the APIs which is the supported endpoint for manipulating the database. After many fruitless internet searches, I reached out to some very helpful Qlik representatives (Thank you, Dustin Baxa). After several iterations of troubleshooting where apps would not upload, or they would upload, but without a Script object, the import worked with the assigned GUID (see code below).

The solution uses Powershell Qlik CLI to make the API calls (note: Qlik does not support the Powershell Qlik CLI, but the API calls it uses are supported). Some of the API endpoints used can only be hit if the script is run on the central node of your Qlik cluster. I hope this helps if you find the need to change the GUIDs in your environments.

#config
$myQVF = "D:\MyQlikApps\MyFile.qvf"
$ID = "5f366e66-7afe-4777-a406-5c5f50d73ca6"
$QlikServerName = 'QLIKCENTRALNODE'

Get-ChildItem cert:CurrentUser\My | Where-Object { $_.FriendlyName -eq 'QlikClient' } | Connect-Qlik $QlikServerName
[System.IO.FileInfo]$SourceApp = $myQVF

# Connect-Qlik -TrustAllCerts
# Figure out where the appimport folder is; we need to place the app here
$QSServiceCluster = Get-QlikServiceCluster -full

# Store the apps folder
$CopyFolder1 = $QSServiceCluster.settings.sharedPersistenceProperties.appFolder

# Store the app import folder used in the /qrs/app/import/ call later
$CopyFolder2 = Invoke-QlikGet "/qrs/app/importfolder"

# Build the empty app entity with our arbitrary GUID
$App = @{
  "id"= $ID
  "name"= 'Arbitrary GUID'
}

$appjson = $App|ConvertTo-Json

# Copy the app from its source location to the Qlik share > Apps path with the target GUID
Copy-Item -Path $SourceApp.FullName -Destination "$($CopyFolder1)\$($app.id)"

# Copy the app from its source location to the app import folder
Copy-Item -Path $SourceApp.FullName -Destination "$($CopyFolder2)\$($SourceApp.Name)"
$FI = [System.IO.FileInfo]::new("$($CopyFolder1)\$($app.id).lock")

# Create a new blank .lock file for the app's target GUID
$null = $FI.Create()

# Create the dummy QRS app entity with the target GUID
Invoke-QlikPost -path "/qrs/app" -body $appjson

# Get the current user's UserID
$user = $env:UserName.ToLower()

# Get the current user's Domain
$domain = $env:UserDomain.ToLower()

# Get the ID used for the current user
$qlikuser = Get-QlikUser -filter "userid eq '$($user)' and userdirectory eq '$($domain)'"

# Build out a dummy script
$scriptjson = '
{
  "owner": {
    "id": "'
$scriptjson += $($qlikuser).id
$scriptjson += '",
  },
  "attributes": "eyJUeXBlIjoiQXBwU2NyaXB0T2JqZWN0IiwiRm9ybWF0IjoiZ3pqc29uIiwiVGl0bGUiOiIiLCJQYXJlbnRJZCI6InF2YXBwX2FwcHNjcmlwdCIsIklzVGVtcG9yYXJ5IjpmYWxzZSwiQ29udGVudEhhc2giOiJiL2t5Y0piZHdDOWJQU2ZTVW9mb0xRYU1FZ2xKU3J3UnhpbncrTVlNVEZNPSJ9",
  "objectType": "app_appscript",
  "app": {
    "id": "'
$scriptjson += $ID
$scriptjson += '",
  },
  "privileges": null,
  "engineObjectId": "qvapp_appscript",
  "contentHash": "P7_S!HF>$],<VT*3,VBIX+\"-+2#*J%P2=+QDPR-QU5",
  "schemaPath": "App.Object"
}'

# Create the dummy script
Invoke-QlikPost -path '/qrs/app/object' -body $scriptjson

# Replace the dummy app with the dummy script with the full app to get all the app's objects (sheets, stories, bookmarks, etc)
Invoke-QlikPost -path "/qrs/app/import/replace?targetappid=$($app.id)" -body "`"$($SourceApp.Name)`""

# Clean up the staged QVF used on the import
Remove-Item -Path "$($CopyFolder2)\$($SourceApp.Name)"
Posted in Blog

Leave a Reply

Your email address will not be published. Required fields are marked *