21 January 2019

Distribute dynamo in an organizational context

There is on occasional basis questions concerning “How to distribute Dynamo and Dynamo packages” especially in an organizational context. The main problem seems to be how to update and maintain that users in a network share the same packages.

I have previously argued for a solution that included “Inno Setup” from http://www.jrsoftware.org this is the same installer used by the dynamo-team to distribute Dynamo, and most important, it is freeware but the developers would be happy for donations :-)

There has been some debate in another post, where I argued that the added article that described how to do it using notepad++ was a very limited solution. I will try to explain why.

Let's say you have an organization where the environment consists of workstations and laptops, a mixed environment, which is very common nowadays.

One option would be that you can have a server location for your packages, but this only solves the problem for the workstations, this is not a good solution for the laptops. Well then use VPN… no, not really. It is a limitation if your environment is dependent on a VPN tunnel for “ordinary” work done on your laptop, this should only be the case in time-limited work processes. This is why licenses are being distributed as they are… usually something there just looks for a valid license but nothing else than that. This is being solved in many ways.

So you are left now with an organization where no one has a shared connection and we want something to work the same way on all computers… especially do we want it to be updated whenever something new is done.

On the other hand do the BIM-manager not want to disturb the IT/Server people over and over again, for updating some odd thing called dynamo…

This is why Inno Setup is the answer. Go talk with the IT admins and let them understand that they need to let the login procedure add two executables. One for the machine startup and one for the user login.

Most of the below-shown code (iss files) can be set up using third parties tools for Inno Setup, but there is some line of Pascal code needed, just copy it, edit it and you should be good to go.

The main idea is, that the BIM manager can maintain--on his/ her personal computer--how the organizational environment should look like. When changes occur, then simply compile new executables and copy the files to the agreed location with the IT admins, Then next time someone restarts/login, then changes will be updated automatically… and individually! However, maintained central :-)

What is being handled on a machine level should include privileges for powerusers or admins, by doing this, then “shared” packages cannot be changed. This can be setup in a way so it still works concerning the “read/write” problem. However, this method can be extended into being a “fixmyproblem” executable users can run from start bottom if something occurs. In that case simply skip checking for the version number.

Dynamo (machine).iss
Preprocessor variables
;My Dynamo path
;Replace --> (your login)
#dim sourcePackages[4]
#define sourcePackages[0] "C:\Users\(your login)\AppData\Roaming\Dynamo\Content\packages"
#define sourcePackages[1] "C:\Users\(your login)\AppData\Roaming\Dynamo\Dynamo Revit\1.3\packages"
#define sourcePackages[2] "C:\Users\(your login)\AppData\Roaming\Dynamo\Dynamo Revit\2.0\packages"

;Dynamo path
;Replace --> (your organization id)
#dim destinationPackages[3]
#define destinationPackages[0] "{commonappdata}\Dynamo\(your organization id)\All\packages"
#define destinationPackages[1] "{commonappdata}\Dynamo\(your organization id)\130\packages"
#define destinationPackages[2] "{commonappdata}\Dynamo\(your organization id)\200\packages"

[Setup]
;SourceDir=..\

;give a valid path where your output exe files should be compiled to!
OutputDir=userdocs:\SoftDev\

OutputBaseFilename=Dynamo_machine
AppName=Dynamo
;Versionnumber must be higher to install updates --> I use dates
AppVersion=2018.1101.0
;Replace --> author
AppCopyright= author
;Replace --> organization
AppPublisher= organization
RestartIfNeededByRun=false
AllowCancelDuringInstall=false
CreateAppDir=false
UsePreviousGroup=false
AppendDefaultGroupName=false
SolidCompression=true
Compression=lzma2/ultra
InternalCompressLevel=ultra
Uninstallable=false

;Normally will changes on a computer level in an organization require more than user privileges
PrivilegesRequired=poweruser
;PrivilegesRequired=none

[InstallDelete]
;Clean old packages
Type: filesandordirs; Name: {#destinationPackages[0]}
Type: filesandordirs; Name: {#destinationPackages[1]}
Type: filesandordirs; Name: {#destinationPackages[2]}

[Files]
;Packages to include
;Do only select those that shold be common, you might have several more packages installed by yourself

;Version specific packages, means that it takes those that are for either 1.3 og 2.0 environment
;In the example is all packages in the "source" folder selected!
Source: {#sourcePackages[1]}\*; DestDir: {#destinationPackages[1]}; Excludes: dyf\backup; Flags: recursesubdirs createallsubdirs
Source: {#sourcePackages[2]}\*; DestDir: {#destinationPackages[2]}; Excludes: dyf\backup; Flags: recursesubdirs createallsubdirs

;Common packages, means that the package is used both in the 1.3 og 2.0 environment
;In the example is specific packages in the "source" selected!
Source: {#sourcePackages[0]}\Dynamic CSharp Interpreter\*; DestDir: {#destinationPackages[0]}\Dynamic CSharp Interpreter; Excludes: dyf\backup; Flags: recursesubdirs createallsubdirs
Source: {#sourcePackages[0]}\archi-lab.net\*; DestDir: {#destinationPackages[0]}\archi-lab.net; Excludes: dyf\backup; Flags: recursesubdirs createallsubdirs
Source: {#sourcePackages[0]}\Data-Shapes\*; DestDir: {#destinationPackages[0]}\Data-Shapes; Excludes: dyf\backup; Flags: recursesubdirs createallsubdirs
Source: {#sourcePackages[0]}\Rhythm\*; DestDir: {#destinationPackages[0]}\Rhythm; Excludes: dyf\backup; Flags: recursesubdirs createallsubdirs
Source: {#sourcePackages[0]}\spring nodes\*; DestDir: {#destinationPackages[0]}\spring nodes; Excludes: dyf\backup; Flags: recursesubdirs createallsubdirs
Source: {#sourcePackages[0]}\Ampersand\*; DestDir: {#destinationPackages[0]}\Ampersand; Excludes: dyf\backup; Flags: recursesubdirs createallsubdirs
Source: {#sourcePackages[0]}\BumbleBee\*; DestDir: {#destinationPackages[0]}\BumbleBee; Excludes: dyf\backup; Flags: recursesubdirs createallsubdirs
Source: {#sourcePackages[0]}\MEPover\*; DestDir: {#destinationPackages[0]}\MEPover; Excludes: Backup\; Flags: recursesubdirs createallsubdirs
Source: {#sourcePackages[0]}\Structural Analysis for Dynamo\*; DestDir: {#destinationPackages[0]}\Structural Analysis for Dynamo; Excludes: dyf\backup; Flags: recursesubdirs createallsubdirs
Source: {#sourcePackages[0]}\Optimo\*; DestDir: {#destinationPackages[0]}\Optimo; Excludes: dyf\backup; Flags: recursesubdirs createallsubdirs
Source: {#sourcePackages[0]}\Honeybee\*; DestDir: {#destinationPackages[0]}\Honeybee; Excludes: dyf\backup; Flags: recursesubdirs createallsubdirs
Source: {#sourcePackages[0]}\Ladybug\*; DestDir: {#destinationPackages[0]}\Ladybug; Excludes: dyf\backup; Flags: recursesubdirs createallsubdirs

[Registry]
;Version number
;Replace --> (your organization id)
Root: HKLM; Subkey: Software\(your organization id) ; ValueType: string; ValueName: Dynamo; ValueData: {#SetupSetting("AppVersion")}

[Code]
//Get the AppVersion
function CurrentVersion(): String;
begin
  Result := ExpandConstant('{#SetupSetting("AppVersion")}');
end;

//Get the Registry value
//Replace --> (your organization id)
function GetInstalledVersion(): String;
     var
         InstalledVersion: String;
     begin
         InstalledVersion:= '';
         RegQueryStringValue(HKLM, 'Software\(your organization id)', 'Dynamo', InstalledVersion);
         Result:= InstalledVersion;
     end;

//On initialize, check if higher value
function InitializeSetup(): boolean;
begin
    if (GetInstalledVersion() < CurrentVersion()) then
        result := true
    else
        result := false;
end;

Dynamo (user).iss
;Preprocessor variables
;My Dynamo path
#dim dynamoPath[2]
#define dynamoPath[0] "Dynamo\Dynamo Revit\1.3"
#define dynamoPath[1] "Dynamo\Dynamo Revit\2.0"

[Setup]
SourceDir=..\

;give a valid path where your output exe files should be compiled to!
OutputDir=userdocs:\SoftDev\

OutputBaseFilename=DTU_Dynamo_user
AppName=Dynamo
;Versionnumber must be higher to install updates --> I use dates
AppVersion=2018.1001.0
;Replace --> author
AppCopyright= author
;Replace --> organization
AppPublisher= organization
RestartIfNeededByRun=false
AllowCancelDuringInstall=false
CreateAppDir=false
UsePreviousGroup=false
AppendDefaultGroupName=false
SolidCompression=true
Compression=lzma2/ultra
InternalCompressLevel=ultra
Uninstallable=false
PrivilegesRequired=none

[Files]
;Default filestructure
;This part is ONLY needed if dynamo hasnt been installed and activated before this executable is being activated! 
;look in the attached zip file for default content needed by the executable is runned before dynamo has been activated!
;Notice the source folder location if you want to use the default (from the zip file) content!
Source: Content\*.*; DestDir: {userappdata}; Flags: createallsubdirs recursesubdirs; Permissions: powerusers-full

[Registry]
;Version number
;Replace --> (your organization id)
Root: HKCU; Subkey: Software\(your organization id); ValueType: string; ValueName: Dynamo; ValueData: {#SetupSetting("AppVersion")}

[code]
//Get the AppVersion
function CurrentVersion(): String;
begin
  Result := ExpandConstant('{#SetupSetting("AppVersion")}');
end;

//Get the Registry value
//Replace --> (your organization id)
function GetInstalledVersion(): String;
     var
         InstalledVersion: String;
     begin
         InstalledVersion:= '';
         RegQueryStringValue(HKCU, 'Software\(your organization id)', 'Dynamo', InstalledVersion);
         Result:= InstalledVersion;
     end;

//On initialize, check if higher value
function InitializeSetup(): boolean;
begin
    if (GetInstalledVersion() < CurrentVersion()) then
        result := true
    else
        result := false;
end;

//Modify the XML document
procedure DynamoXML(const AFileName, APath, AValue: string); 
var
  XMLDocument, XMLNewNode, XMLRootNode : Variant;   
begin 
  XMLDocument := CreateOleObject('Msxml2.DOMDocument.6.0'); 
  XMLDocument.async := False; 
  XMLDocument.load(AFileName); 
  if XMLDocument.parseError.errorCode <> 0 then
    exit;
  XMLNewNode := XMLDocument.createElement('string');
  XMLDocument.setProperty('SelectionLanguage', 'XPath');    
  XMLRootNode := XMLDocument.selectSingleNode(APath); 
  XMLRootNode.appendChild (XMLNewNode);
  XMLRootNode.lastChild.text := AValue; 
  XMLDocument.save(AFileName);
end; 

//On exit, modify
//Replace --> (your organization id)
procedure CurStepChanged(CurStep: TSetupStep);
begin
  if CurStep = ssPostInstall then
  begin
    DynamoXML(ExpandConstant('{userappdata}\{#dynamoPath[0]}\DynamoSettings.xml'),'//BackupFiles',ExpandConstant('{userappdata}\Dynamo\Dynamo Revit\backup\backup.DYN'));
    DynamoXML(ExpandConstant('{userappdata}\{#dynamoPath[0]}\DynamoSettings.xml'),'//CustomPackageFolders',ExpandConstant('{userappdata}\{#dynamoPath[0]}'));
    DynamoXML(ExpandConstant('{userappdata}\{#dynamoPath[0]}\DynamoSettings.xml'),'//CustomPackageFolders',ExpandConstant('{commonappdata}\Dynamo\All'));
    DynamoXML(ExpandConstant('{userappdata}\{#dynamoPath[0]}\DynamoSettings.xml'),'//CustomPackageFolders',ExpandConstant('{commonappdata}\Dynamo\130'));

    DynamoXML(ExpandConstant('{userappdata}\{#dynamoPath[1]}\DynamoSettings.xml'),'//BackupFiles',ExpandConstant('{userappdata}\Dynamo\Dynamo Revit\backup\backup.DYN'));
    DynamoXML(ExpandConstant('{userappdata}\{#dynamoPath[1]}\DynamoSettings.xml'),'//CustomPackageFolders',ExpandConstant('{userappdata}\{#dynamoPath[1]}'));
    DynamoXML(ExpandConstant('{userappdata}\{#dynamoPath[1]}\DynamoSettings.xml'),'//CustomPackageFolders',ExpandConstant('{commonappdata}\Dynamo\All'));
    DynamoXML(ExpandConstant('{userappdata}\{#dynamoPath[1]}\DynamoSettings.xml'),'//CustomPackageFolders',ExpandConstant('{commonappdata}\Dynamo\200'));
  end;
end;
This post is originally published at dynamobim.org