.. Reminder for header structure:
  Parts (H1)          : #################### with overline
  Chapters (H2)        : ******************** with overline
  Sections (H3)        : ====================
  Subsections (H4)    : --------------------
  Subsubsections (H5) : ^^^^^^^^^^^^^^^^^^^^
  Paragraphs (H6)      : """""""""""""""""""""

.. meta::
  :description: Creating WAPT packages code
  :keywords: working, WAPT, personalizing, creating packages, documentation

.. |vscode| image:: wapt-resources/icon_visual-studio-text-editor.png
  :alt: Visual Studio text editor

.. |vscodium| image:: wapt-resources/icon_vscodium-text-editor.png
  :alt: VSCodium text editor

.. |vim| image:: wapt-resources/icon_vim-text-editor.png
  :alt: Vim text editor

.. |pyscripter| image:: wapt-resources/icon_pyscripter-text-editor.png
  :alt: Pyscripter text editor

.. |pycharm| image:: wapt-resources/icon_pycharm-text-editor.png
  :alt: Pycharm text editor

.. |notepad++| image:: wapt-resources/icon_notepadpluplus-text-editor.png
  :alt: Notepad ++ text editor

.. |nano| image:: wapt-resources/icon_nano-text-editor.png
  :alt: Nano text editor

.. role:: green
.. role:: orange
.. role:: red

.. _creating_WAPT_packages_code:

#########################
How to code WAPT packages
#########################

.. _common_setuphelper_functions:

******************************************************
Simple examples of commonly used setuphelper functions
******************************************************

Presentation of several functions implemented in :term:`Setuphelpers` and frequently used to develop WAPT packages.

Testing and manipulating folders and files
==========================================

Creating a path recursively
---------------------------

Command :command:`makepath` makes the path variable for :file:`C:\\Program Files (x86)\\Mozilla\\Firefox`.

.. code-block:: python

  makepath(programfiles,'Mozilla','Firefox')


Creating and destroying directories
-----------------------------------

Command :command:`mkdirs` creates the directory :file:`C:\\test`.

.. code-block:: python

  mkdirs('C:\\test')

Command :command:`remove_tree` destroys the directory :file:`C:\\tmp\\target`.

.. code-block:: python

  remove_tree(r'C:\tmp\target')

Checking if a path is a file or a folder
----------------------------------------

Command :command:`isdir` checks whether :file:`C:\\Program Files (x86)\\software` is a directory.

.. code-block:: python

  isdir(makepath(programfiles32,'software')):
      print('The directory exists')

Command :command:`isfile` checks whether :file:`C:\\Program Files (x86)\\software\\file` is a file.

.. code-block:: python

  isfile(makepath(programfiles32,'software','file')):
      print('file exist')

Checking whether a directory is empty
-------------------------------------

Command :command:`dir_is_empty` checks that directory :file:`C:\\Program Files (x86)\\software` is empty.

.. code-block:: python

  dir_is_empty(makepath(programfiles32,'software')):
      print('dir is empty')


Copying a file
--------------

Command :command:`filecopyto` copies :file:`file.txt` into the :file:`C:\\Program Files (x86)\\software` directory.

.. code-block:: python

  filecopyto('file.txt',makepath(programfiles32,'software'))


Copying a directory
-------------------

Command :command:`copytree2` copies the :file:`sources` folder into the :file:`C:\\projet` directory.

.. code-block:: python

  copytree2('sources','C:\\projet')


Manipulating registry keys
==========================

Checking the existence of a registry key
----------------------------------------

Command :command:`registry_readstring` checks if registry key *{8A69D345-D564-463c-AFF1-A69D9E530F96}* exists in registry path :file:`SOFTWARE\\Google\\Update\\Clients` of *HKEY_LOCAL_MACHINE*.

.. code-block:: python

  if registry_readstring(HKEY_LOCAL_MACHINE, "SOFTWARE\\Google\\Update\\Clients\\{8A69D345-D564-463c-AFF1-A69D9E530F96}", 'pv'):
      print('key exist')

Showing the value of a registry key
-----------------------------------

Command :command:`registry_readstring` reads the value *{8A69D345-D564-463c-AFF1-A69D9E530F96}* stored in the registry path :file:`SOFTWARE\\Google\\Update\\Clients` of *HKEY_LOCAL_MACHINE*.

.. code-block:: python

  print(registry_readstring(HKEY_LOCAL_MACHINE, r'SOFTWARE\Google\Update\Clients\{8A69D345-D564-463c-AFF1-A69D9E530F96}', 'pv'))

Modifying the value of a registry key
-------------------------------------

Command :command:`registry_setstring` modifies the value of the registry key *TOUVersion* stored in the registry path :file:`SOFTWARE\\Microsoft\\Windows Live` of *HKEY_CURRENT_USER*.

.. code-block:: python

  registry_setstring(HKEY_CURRENT_USER, "SOFTWARE\\Microsoft\\Windows Live\\Common",'TOUVersion','16.0.0.0', type=REG_SZ)

Creating and destroying shortcuts
=================================

With WAPT setuphelper it is possible to create different types of shortcuts.

Creating a desktop shortcut for all users
-----------------------------------------

Command :command:`create_desktop_shortcut` creates the shortcut *WAPT Console Management* into :file:`C:\\Users\\Public` directory pointing to :file:`C:\\Program Files (x86)\\wapt\\waptconsole.exe`; the shortcut is available for all users.

.. code-block:: python

  create_desktop_shortcut(r'WAPT Console Management',target=r'C:\Program Files (x86)\wapt\waptconsole.exe')

Removing a desktop shortcut for all users
-----------------------------------------

Command :command:`remove_desktop_shortcut` deletes the *WAPT Console Management* shortcut from the folder :file:`C:\\Users\\Public`; the shortcut is deleted for all users.

.. code-block:: python

  remove_desktop_shortcut('WAPT Console Management')

Firefox places a shortcut on the all users desktop, we are going to delete it.

We will use the :command:`remove_desktop_shortcut` function:

* Modify your :file:`setup.py` and use the function like this.

  .. code-block:: python

    # -*- coding: utf-8 -*-
    from *SetupHelpers* import *

    uninstallkey = []

    def install():
        install_exe_if_needed("Firefox Setup 45.5.0esr.exe",silentflags="-ms",key='Mozilla Firefox 45.5.0 ESR (x64 fr)',min_version="45.5.0")
        remove_desktop_shortcut('Firefox')

* If you restart the installation from :program:`PyScripter`, you will notice that the "all users" desktop shortcut has disappeared.

Creating a menu shortcut for an application
-------------------------------------------

Command :command:`create_programs_menu_shortcut` creates the shortcut *WAPT Console Management* into start menu pointing to :file:`C:\\Program Files (x86)\\wapt\\waptconsole.exe`; the shortcut is available for all users.

.. code-block:: python

  create_programs_menu_shortcut(r'WAPT Console Management',target=r'C:\Program Files (x86)\wapt\waptconsole.exe')

Removing a menu shortcut for an application
-------------------------------------------

Command :command:`remove_programs_menu_shortcut` deletes the *WAPT Console Management* shortcut from start menu.

.. code-block:: python

  remove_programs_menu_shortcut('WAPT Console Management')

Creating a desktop shortcut for a logged in user
------------------------------------------------

.. hint::

  These functions are used in session_setup context.

Command :command:`create_user_desktop_shortcut` creates the shortcut *WAPT Console Management* on user desktop pointing to :file:`C:\\Program Files (x86)\\wapt\\waptconsole.exe`.

.. code-block:: python

  create_user_desktop_shortcut(r'WAPT Console Management',target=r'C:\Program Files (x86)\wapt\waptconsole.exe')

Removing a desktop shortcut for a logged in user
------------------------------------------------

Command :command:`remove_user_desktop_shortcut` deletes the *WAPT Console Management* shortcut from the logged in user's desktop.

.. code-block:: python

  remove_user_desktop_shortcut('WAPT Console Management')

Creating a menu shortcut to an application for a specific user
--------------------------------------------------------------

.. hint::

  These functions are used in session_setup context.

Command :command:`create_user_programs_menu_shortcut` creates the shortcut *WAPT Console Management* on user start menu pointing to :file:`C:\\Program Files (x86)\\wapt\\waptconsole.exe`.

.. code-block:: python

  create_user_programs_menu_shortcut(r'WAPT Console Management',target=r'C:\Program Files (x86)\wapt\waptconsole.exe')

Removing a menu shortcut to an application for a specific user
--------------------------------------------------------------

Command :command:`remove_user_programs_menu_shortcut` deletes the *WAPT Console Management* shortcut from the logged in user's start menu.

.. code-block:: python

  remove_user_programs_menu_shortcut('WAPT Console Management')

Manipulating ini files
======================

Reading a value in a section of a ini file
------------------------------------------

Command :command:`inifile_readstring` will read a value from a key and a section of a ini file.

.. code-block:: python

  inifile_readstring("file.ini","global","key")

Writing a value in a section of a ini file
------------------------------------------

Command :command:`inifile_writestring` will modify a value from a key and a section of a ini file.

.. code-block:: python

  inifile_writestring("file.ini","global","key","value")

Deleting a key in a section of a ini file
-----------------------------------------

Command :command:`inifile_deleteoption` will delete a key in a given section of a ini file.

.. code-block:: python

  inifile_deleteoption("file.ini","global","key")

Deleting an entire section of a ini file
----------------------------------------

Command :command:`inifile_deletesection` will delete a section of a ini file and all of its content.

.. code-block:: python

  inifile_deletesection("file.ini","global")

Windows environment/ Software/ Services
=======================================

Retrieving the version of a file
--------------------------------

Command :command:`get_file_properties` shows package properties.

.. code-block:: python

  get_file_properties(makepath(programfiles32,'InfraRecorder','infrarecorder.exe'))['ProductVersion']

Checking the Windows version
----------------------------

Command :command:`windows_version` checks that the Windows version is strictly inferior to *6.2.0*.

.. code-block:: python

  windows_version()<Version('6.2.0'):

.. hint::

  For more informations you can visit `Microsoft Windows version number <https://docs.microsoft.com/en-us/windows/win32/sysinfo/operating-system-version>`_.

Checking for 64bits architecture
--------------------------------

Command :command:`iswin64` checks that the system architecture is 64bits.

.. code-block:: python

  if iswin64():
      print('Pc x64')
  else:
      print('Pc not x64')

Checking for the Program Files variable
---------------------------------------

* programfiles;

  .. code-block:: python

    print(programfiles())

* programfiles32;

  .. code-block:: python

    print(programfiles32())

* programfiles64;

  .. code-block:: python

    print(programfiles64())

Each command returns a different *ProgramFiles* location.

For example, command :command:`programfiles64` returns native Program Files directory, eg. :file:`C:\\Program Files (x86)` on either win64 or win32 architecture and :command:`programfiles()` will return the path of the 32bit Program Files directory, eg. :file:`Programs Files (x86)` on win64 architecture, and :file:`Programs Files` on win32 architecture.

Checking for the AppData variable
---------------------------------

user_appdata/ user_local_appdata

.. hint::

  These functions are used with :command:`session_setup`

Command :command:`user_appdata` returns roaming *AppData* profile path of logged on user (:file:`C:\\Users\\%username%\\AppData\\Roaming`).

.. code-block:: python

  print(user_appdata())

Command :command:`user_local_appdata` returns the local *AppData* profile path of the logged on user (:file:`C:\\Users\\%username%\\AppData\\Local`).

.. code-block:: python

  print(user_local_appdata())

Disabling temporarily the wow3264 file redirection
--------------------------------------------------

Command :command:`disable_file_system_redirection` disables wow3264 redirection in the current context.

.. code-block:: python

  with disable_file_system_redirection():
      filecopyto('file.txt',system32())

Obtaining the current logged in user
------------------------------------

Command :command:`get_current_user` shows the currently logged on username.

.. code-block:: python

  print(get_current_user())

Obtaining the computer name
---------------------------

Command :command:`get_computername` shows the name of the computer.

.. code-block:: python

  print(get_computername())

Obtaining the AD domain to which the computer is joined
-------------------------------------------------------

Command :command:`get_domain_fromregistry` returns the :abbr:`FQDN (Fully Qualified Domain Name)` of the computer.

.. code-block:: python

  get_domain_fromregistry()

Actions on installed software
=============================

Checking installed software
---------------------------

Command :command:`installed_softwares` returns the list of installed software on the computer from registry in an array.

.. code-block:: python

  installed_softwares('winscp')

.. code-block:: python

  [{'install_location': u'C:\\Program Files\\WinSCP\\', 'version': u'5.9.2', 'name': u'WinSCP 5.9.2', 'key': u'winscp3_is1', 'uninstall_string': u'"C:\\Program Files\\WinSCP\\unins000.exe"', 'publisher': u'Martin Prikryl', 'install_date': u'20161102', 'system_component': 0}]

Obtaining the uninstall command from registry
---------------------------------------------

Command :command:`uninstall_cmd` returns the silent uninstall command.

.. code-block:: python

  uninstall_cmd('winscp3_is1')

.. code-block:: bash

  "C:\Program Files\WinSCP\unins000.exe" /SILENT

Uninstalling software
---------------------

.. code-block:: python
  
  for to_uninstall in installed_softwares(name="winscp"):
        if Version(to_uninstall["version"]) < Version(control.get_software_version()):
            print(f"Removing: {to_uninstall['name']} ({to_uninstall['version']})")
            killalltasks(ensure_list(control.impacted_process))
            run(uninstall_cmd(to_uninstall["key"]))
            wait_uninstallkey_absent(to_uninstall["key"])

* For each item of the list return by *installed_softwares* containing keyword *winscp*.

* If the version is lower than the value of the control.get_software_version.

* A message is displayed to indicate which software and which version are being uninstalled.

* killalltasks(ensure_list(control.impacted_process)) stops all processes associated with the software before uninstallation. This ensures that the software is not in use.

* run(uninstall_cmd(to_uninstall[‘key’])) executes the uninstall command for the software. the wait_uninstallkey_absent function is used to ensure that the key is completely uninstalled to ensure that the software is uninstalled.


Killing tasks
-------------

Command :command:`killalltasks` kills all tasks with the specified name.

.. code-block:: python

  killalltasks('firefox')

Using control file fields
=========================

It is possible to use control file informations on :file:`setup.py`.

Obtaining packages version
--------------------------

.. code-block:: python

  def setup():
      print(control['version'])

Command :command:`print(control['version'])` shows the *version* value from the :file:`control` file.

.. code-block:: python

  def setup():
      print(control['version'].split('-',1)[0])

Command :command:`print(control['version'].split('-',1)[0])` shows the software version number without the WAPT version number from the :file:`control` file.

Obtaining software title names
------------------------------

.. todo::

  upcoming documentation

Managing a WAPT package with another WAPT package
=================================================

Installing a package
--------------------

.. code-block:: python

  WAPT.install('tis-scratch')

Command :command:`install` installs a WAPT package on the selected computer.

Removing a package
------------------

.. code-block:: python

  WAPT.remove('tis-scratch')

Command :command:`remove` uninstalls a WAPT package from the selected computer.

Forgetting a package
--------------------

.. code-block:: python

  WAPT.forget_packages('tis-scratch')

Command :command:`forget_packages` informs the WAPT Agent to forget a WAPT package on the selected computer.

.. hint::

  If the desired result is to remove *tis-scratch*, you should either reinstall the package (:code:`wapt-get install "tis-scratch"`) then remove it (:command:`wapt-get remove "tis-scratch"`), either removing it manually from the Control Panel menu :menuselection:`Add/ Remove Programs`.

********************
Improving my package
********************

Copying a file
==============

It is possible to configure :program:`Firefox` with a :file:`policies.json` file.
See `<https://github.com/mozilla/policy-templates/blob/master/README.md>`_.

This file **MUST** be placed in the :file:`distribution` folder at the root of Firefox.

To help you create this :file:`policies.json` file you can use the `enterprise policy generator <https://addons.mozilla.org/fr/firefox/addon/enterprise-policy-generator/>`_ generator for Firefox.

When you have generated your :file:`policies.json` file, place it in :file:`c:\\waptdev\\prefix-firefox-esr-wapt\\policies.json`.

The :file:`distribution` folder at the root of Firefox may not exist, so we will test its existence and create it with the :command:`mkdirs` command if it does not exist:

.. code-block:: python

   if not isdir(r'C:\Program Files\Mozilla Firefox\distribution'):
       mkdirs(r'C:\Program Files\Mozilla Firefox\distribution')

.. important::

  If you have backslashes in your path, you should always put an **r** in front of the string, like in the previous example.

You will also need to use the ``filecopyto`` function to copy the :file:`policies.json` file:

.. code-block:: python

   filecopyto('policies.json',r'C:\Program Files\Mozilla Firefox\distribution')

.. hint::

   There is no need to put the full path for the source file since the :file:`policies.json` file is at the root of the WAPT package, so we use the relative path.

Modify your :file:`setup.py`:

.. code-block:: python

  # -*- coding: utf-8 -*-
  from setuphelpers import *

  uninstallkey = []

  def install():
      install_exe_if_needed("Firefox Setup 45.5.0esr.exe",silentflags="-ms",key='Mozilla Firefox 45.5.0 ESR (x64 fr)',min_version="45.5.0")
      remove_desktop_shortcut('Firefox')

      if not isdir(r'C:\Program Files\Mozilla Firefox\distribution'):
          mkdirs(r'C:\Program Files\Mozilla Firefox\distribution')

      filecopyto('policies.json',r'C:\Program Files\Mozilla Firefox\distribution')

Your package is now ready to apply a configuration.
You can launch an installation with :program:`PyScripter` and validate that the package works according to your objective.

Finally, launch your :program:`Firefox` to verify that it will work for your users.

Uninstalling unwanted versions
==============================

.. hint::

  At each step of these examples you can run an installation to test the result.

In our case we want to uninstall the non ESR version of :program:`Firefox`.

We will look for the other software installed on the host to check if a non-esr version of :program:`Firefox` is installed.

To reproduce our example, download and install the `latest consumer version of Firefox <https://download.mozilla.org/?product=firefox-latest-ssl&os=win>`_:

* To search unwanted version of :program:`Firefox` we will use the ``installed_softwares`` function.
  This function returns a dictionary list containing the software properties:

  .. code-block:: python

     print(installed_softwares('Firefox'))

     [
        {
      'install_date': '',
        'install_location': 'C:\\Program Files\\Mozilla Firefox',
        'key': 'Mozilla Firefox 78.7.1 ESR (x64 fr)',
        'name': 'Mozilla Firefox 78.7.1 ESR (x64 fr)',
        'publisher': 'Mozilla',
        'system_component': 0,
        'uninstall_string': '"C:\\Program Files\\Mozilla Firefox\\uninstall\\helper.exe"',
        'version': '78.7.1',
        'win64': True
      },

        {
       'install_date': '',
         'install_location': 'C:\Program Files (x86)\\Mozilla Firefox',
         'key': 'Mozilla Firefox 79.0 (x86 fr)',
         'name': 'Mozilla Firefox 79.0 (x86 fr)',
         'publisher': 'Mozilla',
         'system_component': 0,
         'uninstall_string': '"C:\Program Files (x86)\\Mozilla Firefox\\uninstall\\helper.exe"',
         'version': '79.0',
         'win64': False
       }
     ]

* Check the name of each software.

  .. code-block:: python

     for uninstall in installed_softwares('Mozilla Firefox'):
         print(uninstall['name'])

* Show the name of each software found.

  .. code-block:: python

     for uninstall in installed_softwares('Mozilla Firefox'):
         if not 'ESR' in uninstall['name']:
             print(uninstall['name'])

* Show the name of each software found which does not include the string *ESR* in its name and its uninstallkey.

  .. code-block:: python

     for uninstall in installed_softwares('Mozilla Firefox'):
         if not 'ESR' in uninstall['name']:
             print(uninstall['name'])
             print('Uninstall ' + uninstall['key'])

We will now use a WAPT trick using the :command:`uninstall_cmd` function:

* Install cmd accepts an uninstall key as an argument and will send the command to run to start the silent uninstall.

  .. code-block:: python

        for uninstall in installed_softwares('Mozilla Firefox'):
            if not 'ESR' in uninstall['name']:
                print(uninstall['name'])
                print('Uninstall ' + uninstall['key'])
                silent_uninstall = uninstall_cmd(uninstall['key'])
                print('Run ' + silent_uninstall)

* Start the uninstallation.

  .. code-block:: python

        for uninstall in installed_softwares('Mozilla Firefox'):
            if not 'ESR' in uninstall['name']:
                print(uninstall['name'])
                print('Uninstall ' + uninstall['key'])
                silent_uninstall = uninstall_cmd(uninstall['key'])
                print('Run ' + silent_uninstall)
                run(silent_uninstall)

We can also uninstall the Mozilla maintenance service:

.. code-block:: python

      for uninstall in installed_softwares('MozillaMaintenanceService'):
          run(uninstall_cmd(uninstall['key']))

* Finally, modify your :file:`setup.py`:

  .. code-block:: python

    # -*- coding: utf-8 -*-
    from setuphelpers import *

    uninstallkey = []

    def install():
        #Install firefox if necessary
        install_exe_if_needed("Firefox Setup 45.5.0esr.exe",silentflags="-ms",key='Mozilla Firefox 45.5.0 ESR (x64 fr)',min_version="45.5.0")

        #Removal of the firefox shortcut on the all user desktop
        remove_desktop_shortcut('Firefox')

        #Creation of the distribution folder if it does not exist
        if not isdir(r'C:\Program Files\Mozilla Firefox\distribution'):
            mkdirs(r'C:\Program Files\Mozilla Firefox\distribution')

        #Copy of the policies.json file found at the root of the package in the destination of the distribution folder
        filecopyto('policies.json',r'C:\Program Files\Mozilla Firefox\distribution')

        #For each Mozilla Firefox installed
        for uninstall in installed_softwares('Mozilla Firefox'):
            #If the software does not have the word ESR in the name
            if not 'ESR' in uninstall['name']:
                print(uninstall['name'])
                print('Uninstall ' + uninstall['key'])

                #Looking for how we can uninstall it silently
                silent_uninstall = uninstall_cmd(uninstall['key'])
                print('Run ' + silent_uninstall)

                #We launch the previous command.
                run(silent_uninstall)

        #Uninstalling mozilla maintenance service
        for uninstall in installed_softwares('MozillaMaintenanceService'):
            run(uninstall_cmd(uninstall['key']))

Your code now handles the uninstallation of unwanted versions of :program:`Firefox`.

Improving setup.py to use variables
===================================

Examples of variable usage:

.. code-block:: python

   version_firefox = "45.0"

   uninstallkey = "Mozilla Firefox " + version_firefox + " ESR (x64 fr)"
   print(uninstallkey)

   uninstallkey = "Mozilla Firefox %s ESR (x64 fr)" % (version_firefox)
   print(uninstallkey)

   uninstallkey = "Mozilla Firefox {} ESR (x64 fr)".format(version_firefox)
   print(uninstallkey)

   uninstallkey = f"Mozilla Firefox {version_firefox} ESR (x64 fr)"
   print(uninstallkey)

.. important::

   The last example is the best example but this operation only works with :program:`Python3`.

We can now use variables in our :file:`setup.py`:

  .. code-block:: python

    # -*- coding: utf-8 -*-
    from setuphelpers import *

    uninstallkey = []

    def install():

        version_firefox = "45.5.0"

        #Install firefox if necessary
        install_exe_if_needed("Firefox Setup %sesr.exe" % version_firefox,silentflags="-ms",key='Mozilla Firefox %s ESR (x64 fr)' % version_firefox,min_version=version_firefox)

        #Removal of the firefox shortcut on the all user desktop
        remove_desktop_shortcut('Firefox')

        distribution_folder=r'C:\Program Files\Mozilla Firefox\distribution'

        #Creation of the distribution folder if it does not exist
        if not isdir(distribution_folder):
            mkdirs(distribution_folder)

        ... The rest of the code does not change ...

.. hint::

  You can retrieve the version number shown in the :file:`control` file like this:

  .. code-block:: python

    version_firefox = control.get_software_version()

.. _user_session_setup:

Customizing the user environment
================================

It is sometimes necessary to customize a software in user context to set specific settings or to comply to the Organization's rules and preferences:

* Creating user desktop shortcut with specific arguments.

* Making changes to user Windows registry keys.

* Making changes to files, to browser settings of the user.

* Configuring shortcuts to the Organization's set of templates for Documents, Spreadsheets or Presentations in Office Suites to encourage or insure that editorial and graphical guidelines are followed.

* Setting up the user's email or instant messaging from the Organization's main user data repository (LDAP directory, database, etc).

* Customizing an office suite or business software based on the Organization's main user data repository (LDAP directory, database, etc).

The *session_setup* function benefits from the power of python to achieve a high level of automation.

Principles of *session_setup*
-----------------------------

The WAPT *session_setup* function is executed for each user using:

.. code-block:: bash

  C:\Program Files (x86)\wapt\wapt-get.exe session-setup ALL

Calling that function executes the *session_setup* script defined within each WAPT package installed on the computer.

The WAPT Agent stores in its local database (:file:`C:\\Program Files (x86)\\wapt\\waptpublicdb.sqlite`) the instruction sets of all WAPT packages.

.. attention::

  The *session_setup* script is launched only **once per WAPT package version and per user**.

  The WAPT Agent stores in is local :file:`%appdata%\\wapt\\waptsession.sqlite` database the instances of the *session_setup* scripts that have been already been played.

Output example of :code:`wapt-get session-setup ALL`:

.. note::

  The logged in user *session_setup* has already previously been launched.

.. code-block:: bash

  wapt-get session-setup ALL

  Configuring tis-7zip ... No session-setup. Done
  Configuring tis-ccleaner ... Already installed. Done
  Configuring tis-vlc ... No session-setup. Done
  Configuring tis-tightvnc ... No session-setup. Done
  Configuring tis-paint.net ... No session-setup. Done
  Configuring wsuser01.mydomain.lan ... No session-setup. Done

Using *session_setup*
---------------------

The *session_setup* scripts are located in the section *def session_setup()* of the :file:`setup.py` file:

Example:

.. code-block:: python

  def session_setup():
    registry_setstring(HKEY_CURRENT_USER, "SOFTWARE\\Microsoft\\Windows Live\\Common",'TOUVersion','16.0.0.0', type=REG_SZ)

.. attention::

  With :command:`session_setup`, there is no possibility to call files contained inside the WAPT package.

  To call external files when uninstalling, copy and paste the needed files in an external folder during the package installation process (example: :file:`c:\\cachefile`).

Example: creating a personalized desktop shortcut
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

One of the possibilities offered by :term:`Setuphelpers` is adding personalized shortcuts on user desktops, instead of a desktop shortcut common to all users.

For that purpose, we will use the :code:`create_user_desktop_shortcut()` function to create shortcuts containing the username and passing a website as an argument to Firefox.

.. code-block:: python
  :emphasize-lines: 9-10

  # -*- coding: utf-8 -*-
  from setuphelpers import *

  uninstallkey = []

  def install():
      install_exe_if_needed("Firefox Setup 45.5.0esr.exe",silentflags="-ms",key='Mozilla Firefox 45.4.0 ESR (x64 fr)',min_version="45.5.0")

  def session_setup():
    create_user_desktop_shortcut("Mozilla Firefox de %s" % get_current_user(),r'C:\Program Files\Mozilla Firefox\firefox.exe',arguments="-url https://tranquil.it")

* Now start the ``session-setup`` directly from :program:`PyScripter`.

  .. figure:: wapt-resources/windows_pyscripter_run-session-setup_menu-item.png
     :align: center
     :alt: PyScripter - running session-setup

     PyScripter - running session-setup

* Finally, check that the icon is present on the desktop.

Using *session_cleanup*
-----------------------

The *session_cleanup* scripts are located in the section *def session_setup()* of the :file:`setup.py` file:

The purpose of this function is to **remove the modifications you made in the user context during the installation of your package**.

To uninstall a package, you first need to uninstall it. **Upon the next user login, session_setup will re-execute and apply the session_cleanup function**. 

.. note::

  If you wish to force this process, you can run :command:`wapt-get session-setup ALL`.

Example: removing a personalized desktop shortcut
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. code-block:: python

  # -*- coding: utf-8 -*-
  from setuphelpers import *

  uninstallkey = []

  def install():
      install_exe_if_needed("Firefox Setup 45.5.0esr.exe",silentflags="-ms",key='Mozilla Firefox 45.4.0 ESR (x64 fr)',min_version="45.5.0")

  def session_setup():
    create_user_desktop_shortcut("Mozilla Firefox de %s" % get_current_user(),r'C:\Program Files\Mozilla Firefox\firefox.exe',arguments="-url https://tranquil.it")
  
  def session_cleanup():
    remove_user_desktop_shortcut("Mozilla Firefox de %s" % get_current_user())

In this example:

session_setup() creates a personalized desktop shortcut for the user.

session_cleanup() removes the personalized desktop shortcut when the package is uninstalled.


.. _auditing_packages_for_compliance:

Using the audit functions for compliance |enterprise_feature|
=============================================================

The audit function allows to make regular checks to desktop configurations and to centralize the results of these checks in the WAPT Console.
This feature allows you to ascertain that your installed base of hosts matches your set of conformity rules over time.

For example you can:

* Regularly check the list of local administrators on the desktops.

* Ascertain over time the correct configuration of a critical software.

* Regularly check the presence of the correct version of a piece of software.

* Ascertain the security settings of a workstation.

The :code:`audit` function benefits from the depth and the breadth of python libraries for unmatched levels of precision and finesse for your auditing needs.

Working principle
-----------------

The :code:`audit` tasks are launched once after every :command:`wapt-get upgrade`, then regularly as defined by the :code:`audit_schedule` attribute.

To manually launch an audit check, you may also use the following command:

.. code-block:: bash

   wapt-get audit

.. note::

  By default, the :code:`audit` function will not launch if the audit is not necessary.

  To force the execution, you may launch the following command:

  .. code-block:: bash

     wapt-get audit -f

The :code:`audit` script is defined in the package's :file:`setup.py` with a function :code:`def audit()`:

In this example, we are improving the Firefox package previously studied in this documentation.

* Add the :code:`audit` function in the :file:`setup.py`.

  .. code-block:: python

     def audit():
         if isfile(r'C:\Program Files\Mozilla Firefox\distribution\policies.json'):
             print('File policies.json found')
             return "OK"
         else:
             print('File policies.json not found')
             return "ERROR"

* Start the audit from :program:`PyScripter`.

  .. image:: wapt-resources/windows_pyscripter_run-audit_menu-item.png
     :align: center
     :alt: PyScripter - Running an audit

* Test with the file then delete
  the :file:`C:\\Program Files\\Mozilla Firefox\\distribution\\policies.json`
  file and test again with :program:`PyScripter`.

You can directly see the status of the audit in the WAPT Console (Click on the package then on the audit column):

.. figure:: wapt-resources/wapt_console_audit_container-window.png
   :scale: 75%
   :align: center
   :alt: Checking an audit status in the WAPT Console

   Checking an audit status in the WAPT Console

The audit function returns one of these 3 values:

* **OK**;
* **WARNING**;
* **ERROR**.

.. attention::

  With the *audit* function, it is not possible to use files that are contained in the WAPT packages.

  To use files embedded in the WAPT package that will be used for an audit, you **MUST** instruct to copy the file(s) to a temporary folder when the WAPT package installs.

Planning an audit
-----------------

The *audit* tasks are launched once after every *upgrade*, then regularly as defined with the :code:`audit_schedule` value.

The value is contained in the :file:`control` file of the WAPT package.

By default, if :code:`audit_schedule` is empty, the audit task can be launched manually from the WAPT Console or be launched automatically if you have defined the option :code:`waptaudit_task_period` in the :file:`wapt-get.ini` of the WAPT Agent.
For more information about the last method, please see :ref:`this documentation <wapt_get_ini_full_options>`.

Otherwise, the periodicity may be indicated in several ways:

* An integer (in minutes).

* An integer followed by a letter (m = minutes, h = hours , d = days , w = weeks).

Default behavior of the audit function
--------------------------------------

By default, the only audit function checks the presence of *UninstallKey* for its WAPT package.

This way, WAPT ascertains that the software is still present on the host, according to the host configuration.

.. _manipulate_audit_data:

Auditing configurations to insure compliance |enterprise_feature|
=================================================================

The :code:`audit_data` function allows to make regular checks to desktop configurations and to centralize the results of these checks in the WAPT Console.
There is historization and you can encrypt your data and decrypt it with your WAPT certificate.

For example you can:

* Change an administrator password, encrypt information and display it on your WAPT Console.

* Regularly check the modification your computer needs like CVE or GLPI inventory.

* Ascertain the security settings of a workstation and historize issues.

  The :code:`audit_data` function is usable in the :code:`audit` function only.

Working principle
-----------------

The :code:`audit_data` functions are launched if they are defined in the :code:`def audit()` section of the :file:`setup.py` file.

On the server side, audit data is stored in the HostAuditData table.
The content of the table can be queried using the :guilabel:`Reporting` tab in the WAPT Console.
The Data is automatically purged according to expiration date.
When WAPT host :code:`update_status()` is launched, the newer audit data is sent to the WAPT Server.

On the Client side, the audit data is stored in the host database with an expiration date (date_expiration) and the max count (max_count) of the stored data is defined in the code.

In this example, we are checking public IP on the computer.

* Add the :code:`audit_data` function inside the :code:`audit` function in the :file:`setup.py`.

  .. code-block:: python

    def audit():
      ip = wgets('https://api.ipify.org',verify_cert=False)
      print(f'My public IP address is: {ip}')
      WAPT.write_audit_data_if_changed('Public IP','log for %s' % get_computername(),ip,max_count=5)
      return 'OK'

Here are the functions related to :code:`audit_data`: 

  .. code-block:: python

    def write_audit_data_if_changed(self, section, key, value, ptype=None, value_date=None, expiration_date=None, max_count=2, keep_days=None):
    """Write data only if different from last one
    """
    def write_audit_data(self, section, key, value, ptype=None, value_date=None, expiration_date=None, max_count=2, keep_days=None):
    """Stores in database a metrics, removes expired ones

        Args:
            section (str)
            key (str)
            value (any)
            value_date
            expiration_date (str) : expiration date of the new value
            max_count (int) : keep at most max_count value. remove oldest one.
            keep_days (int) : set the expiration date to now + keep_days days. override expiration_date arg if not None

        Returns:
            None
    """
    def read_audit_data(self, section, key, default=None, ptype=None):
        """Retrieve the latest value associated with section/key from database"""

    def read_audit_data_set(self, section, key):
        """Retrieve all the values associated with section/key from database"""

    def delete_audit_data(self, section, key):

    def read_audit_data_since(self, last_query_date=None):
        """Retrieve all the values since a date from database"""

.. _def_update:

Updating automatically a software package
=========================================

.. note::

  This part of the documentation is for advanced users of WAPT.

The :code:`update_package` functions are very practical, they allow to gain a lot of time when needing to update a WAPT package with the most recent version of a piece of software.

Working principle
-----------------

The *update_package* function will:

* Fetch online the latest version of the software.

* Download the latest version of the software binaries.

* Remove old versions of the software binaries.

* Update the version number of the software in the :file:`control` file.

If you base your *install* function on the version number inside the :file:`control` file, then you do not even need to modify your :file:`setup.py`.

You just have to do your usual Quality Assurance tests before you :command:`build-upload` your new package.

Example
-------

Here is the *update_package* script for :program:`firefox-esr` as an example:

.. code-block:: python

  def update_package():
        import re,requests,glob

        #Retrieving the last file name
        url = requests.head('https://download.mozilla.org/?product=firefox-esr-latest&os=win64',proxies={}).headers['Location']
        filename = url.rsplit('/',1)[1].replace('%20',' ')

        #download of it if is not in the package
        if not isfile(filename):
            print('Downloading %s from %s'%(filename,url))
            wget(url,filename)

        #removing old exe with wrong name
        for fn in glob.glob('*.exe'):
            if fn != filename:
                remove_file(fn)

        # updates control version from filename, increment package version.
        control.version = '%s-0'%(re.findall('Firefox Setup (.*)esr\.exe',filename)[0])
        control.save_control_to_wapt()

You may launch the *update_package* in :program:`PyScripter`:

.. figure:: wapt-resources/windows_pyscripter_run-update-package-source_menu-item.png
  :align: center
  :alt: PyScripter - Running an update-package-source

  PyScripter - Running an update-package-source

You will find many inspiring examples of *update_package* scripts in packages hosted in the `Tranquil IT store <https://store.wapt.fr/>`_.

.. _installing_portable_software:

Deploying a portable software with WAPT
=======================================

A good example of a WAPT package is a self-contained/ *portable* software package:

* Create the folder for the software in :file:`C:\\Program Files (x86)`.

* Copy the software in that folder.

* Create the shortcut to the application.

* Manage the uninstallation process for the application.

* Close the application if it is running.

Example with ADWCleaner
-----------------------

First, download `Adwcleaner <https://downloads.malwarebytes.com/file/adwcleaner>`_.

You can then generate your package template, please refer to the :ref:`documentation for creating packages from the WAPT Console <create_package_from_console>`.

The file :file:`C:\\waptdev\\tis-adwcleaner-wapt` is created.

Here you will find an example of a portable package that takes almost all the WAPT functions of a :file:`setup.py`:

.. code-block:: python

   from setuphelpers import *

   uninstallkey = []

   exe_name = 'AdwCleaner.exe'
   path_adw = makepath(programfiles,'AdwCleaner')
   path_exe = makepath(path_adw,exe_name)
   nameshortcut = 'AdwCleaner'

   def install():
       mkdirs(path_adw)
       filecopyto(exe_name,path_exe)
       create_desktop_shortcut(nameshortcut,path_exe)

   def uninstall():
       remove_tree(path_adw)
       remove_desktop_shortcut(nameshortcut,path_exe)

   def audit():
       if not isfile(path_exe):
           print('File not found')
           return "OK"
       else:
           print('File Found')
           return "ERROR"

   def update_package():
       wget('https://downloads.malwarebytes.com/file/AdwCleaner',exe_name)
       control.version = get_file_properties(exe_name)['FileVersion'] + '-0'
       control.save_control_to_wapt()

.. _simple_msu_packaging:

Packaging Windows Update .msu packages
======================================

.. hint::

  Pre-requisites: to build WAPT packages, :ref:`the WAPT development environment MUST be installed <envdev_setup>`.

Between *Patch Tuesday* releases, Microsoft may release additional KBs or critical updates that will need to be pushed to hosts quickly.

For that purpose, WAPT provides a package template for :mimetype:`.msu` files.

In that example, we use the KB4522355 downloaded from Microsoft Catalog website.

* `Download KB4522355 MSU package from Microsoft Catalog website <https://www.catalog.update.microsoft.com/Search.aspx?q=KB4522355>`_.

* Create a WAPT package template from the downloaded :mimetype:`.msu` file.
  In the WAPT Console, click on :menuselection:`Tools --> Package Wizard`.

  .. figure:: wapt-resources/wapt_console_make-package-template_menu-option.png
    :scale: 75%
    :align: center
    :alt: PyScripter - WAPT Console window for creating a package template

    PyScripter - WAPT Console window for creating a package template

* Select the downloaded :mimetype:`.msu` package and fill in the required fields.

  .. figure:: wapt-resources/wapt_console_package-wizard-msu_dialog-box.png
    :scale: 75%
    :align: center
    :alt: Informations required for creating the MSU package

    Informations required for creating the MSU package

* Click on :guilabel:`Make and edit` (recommended) to launch package customization.

* WAPT package IDE is launched using the source code from the pre-defined :mimetype:`.msu` template.

* As usual with WAPT packages, test, then build, then sign, then upload and finally affect the desired WAPT packages to your selected hosts and it is done!!

* If the KB becomes bundled with the following *Patch Tuesday*, you can select the hosts onto which the package has been applied and forget the KB package on the hosts.

.. _linux_packaging:

Packaging simple Linux packages
===============================

Before starting, we assume several conditions:

* You have a graphical interface on your Linux system that you use for developing and testing packages.

* You have installed the :program:`vscode` package from the Tranquil IT repository.

* Your user is named *linuxuser* and is a member of the *sudoers* group.

Creating a base template from you linux computer
------------------------------------------------

* Start up a Command Line utility.

* As *linuxuser*, create a WAPT package template.

  .. code-block:: bash

    wapt-get make-template <template_name>

  .. warning::

    **Do not launch this command as root or with a sudo.**

  When you create a template, there will be several files in the :mimetype:`.vscode` folder inside the WAPT package folder:

  * :file:`settings.json`;
  * :file:`launch.json`.

  Example with :program:`VLC`:

  .. code-block:: bash

    wapt-get make-template "tis-vlc"

    Using config file: /opt/wapt/wapt-get.ini
    Template created. You can build the WAPT package by launching
    /opt/wapt//wapt-get.py build-package /home/linuxuser/waptdev/tis-vlc-wapt
    You can build and upload the WAPT package by launching
    /opt/wapt//wapt-get.py build-upload /home/linuxuser/waptdev/tis-vlc-wapt

  .. hint::

    All WAPT packages are stored in *linuxuser*'s home (home of the currently logged in user).

 * VSCode loads up and opens the WAPT package project.

.. figure:: wapt-resources/windows_vscode_vlc_text-terminal-window.png
  :scale: 60%
  :align: center
  :alt: VSCode opening with focus on the *setup* file

  VSCode opening with focus on the *setup* file

* Check the :file:`control` file content.

  You have to give a :code:`description` to the WAPT package, define the :code:`os_target` and the :code:`version` of the WAPT package.

  .. hint::

    :code:`os_target` for unix is *linux*.

  .. warning::

    The software :code:`version` number in your :file:`control` file **MUST** start at 0, and not the version number of the software title, as the version number may not be the same as displayed in the DEB / YUM repository.

  * Original :file:`control` file.

    .. literalinclude:: wapt-resources/package-linux-control_origin.txt
      :emphasize-lines: 2

  * Modified :file:`control` file.

    .. literalinclude:: wapt-resources/package-linux-control_modified.txt
      :emphasize-lines: 2,6,7

  .. note::

    It is to be noted that a sub-version *-1* has been added.
    It is the packaging version of the WAPT package.

    It allows the WAPT package Developer to release several WAPT package versions of the same software, very useful for very rapid and iterative development.

* Make changes to the code in the :file:`setup.py` file accordingly.

  .. code-block:: python

    :emphasize-lines: 8
    # -*- coding: utf-8 -*-
    from setuphelpers import *

    uninstallkey = []

    def install():
        apt_install('vlc')

* Save the package.

Managing the uninstallation
---------------------------

* Make changes to the :file:`setup.py` file with an uninstall.

 .. code-block:: python

   def uninstall():
   apt_remove('vlc')

* Launch a :guilabel:`remove` from VSCode :guilabel:`Run Configurations`.

  .. figure:: wapt-resources/windows_vscode_package_menu-item.png
    :align: center
    :alt: After uninstallation, the software is correctly removed

    After uninstallation, the software is correctly removed

* Check that the software has been correctly removed.

  .. code-block:: bash

    dpkg -l | grep vlc

.. hint::

  In the :command:`uninstall()` function, it is not possible to call for files included inside the WAPT package.
  To call files from the package, it is necessary to copy/ paste the files in a temporary directory during package installation.

Managing the session-setup
--------------------------

* Make changes to the :file:`setup.py` file with a :code:`session-setup`;

  In this example, we will create a file :file:`vlcrc` by default in the user profile.

  .. code-block:: python

    def session_setup():
      vlcrc_content="""[qt] # Qt interface
    qt-notification=0
    qt-privacy-ask=0
    metadata-network-access=0
    """

      vlcdir = os.path.join(os.environ['HOME'], '.config', 'vlc')
      path_vlrc = makepath(vlcdir,'vlcrc')
      ensure_dir(vlcdir)
      if not isfile(path_vlrc):
          with open(makepath(vlcdir,'vlcrc')) as f:
              f.write(vlcrc_content)

* Launch a :guilabel:`session-setup` from VSCode :guilabel:`Run Configurations`.

  .. figure:: wapt-resources/windows_vscode_package_menu-item.png
    :align: center
    :alt: After uninstallation, the software is correctly removed

    After uninstallation, the software is correctly removed

Building and uploading the WAPT package
---------------------------------------

You will find the WAPT package in your :file:`~/waptdev` folder.

You need to transfer the WAPT package folder to the Windows host that has the private key that you use to sign your WAPT packages.

Then, please refer to the :ref:`documentation for building and uploading packages from the WAPT Console <build_upload_from_console>`.

.. _encryting_sensitive_data_in_package:

Encrypting sensitive data contained in a WAPT package |enterprise_feature|
==========================================================================

.. warning::

  This part of the documentation is for advanced users of WAPT.


What is the purpose for doing that?
-----------------------------------

With WAPT, the integrity of the package is ensured. A package whose content has been modified without being re-signed will systematically be refused by the WAPT client.

On the other hand, the content of a WAPT package is not encrypted and will be readable by everyone.
This technical model of transparency brings nevertheless many benefits.

This can be annoying in the case of a package that contains a password, a license key, or any sensitive or confidential data.

Fortunately, **we have a solution**!

Working principle
-----------------

When a WAPT Agent registers with the WAPT Server, it generates a private key/ public certificate pair in :file:`C:\\Program Files (x86)\\wapt\\private`.

* The certificate is sent to the WAPT Server with the inventory when the WAPT client is first registered.

* The private key is kept by the Agent and is only readable locally by the :term:`Local Administrators`.

We will therefore encrypt the sensitive data contained inside the package with the certificate belonging to the host.

During installation, the WAPT Agent will be able to decrypt the sensitive data using its private key.

With this mode of operation, the WAPT Server and secondary repositories have no knowledge of the sensitive data.

Practical case
--------------

You will find here an example of a WAPT package where we encrypt a string of text in an :command:`update_package` function and then decrypt this text in the :command:`install` function.

In this example, the :command:`update_package` function allows us to browse the WAPT Server database to retrieve the certificate from each host and then encrypt the sensitive text with it.

The encrypted text for each host is then stored in a :file:`encrypt-txt.json` file at the root of the WAPT package.

When the WAPT package installs, the WAPT Agent will take the encrypted text and decipher it with his own private key.

You can test it by yourself by downloading the example package `tis-encrypt-sample <https://wapt.tranquil.it/store/fr/tis-encrypt-sample>`_.


.. attention::

  The python output (log install of the WAPT package) is readable by the users on the host, so **you should not display the deciphered text with a print during installation**.


.. _task_schedule_in_package:

Import a Task Schedule in a WAPT Package
========================================

WAPT often operates on the principle of recurrence, so an action like an audit is performed regularly (every two hours by default). However, this recurrence is not as precise as an order indicating that the action should be performed every day at exactly 19:00. If you want to perform an action in a very specific context, we suggest integrating a scheduled task into a WAPT package.

Creating the Scheduled Task
---------------------------

*Step 1: Open Task Scheduler*

- Open Task Scheduler by searching for "Task Scheduler" in the Start menu.
- Click on "Create Task" in the right panel.

*Step 2: Configure the Task*

- **General**:

  - Name your task.
  - Select **SYSTEM** as the user.
  - Check "Run with highest privileges".

- **Triggers**:

  - Configure the triggers according to your needs (for example, daily at 19:00).

- **Actions**:

  - Add the actions that the task should perform (for example, launch C:\Program Files(x86)\waptexit.exe).


*Step 3: Export the Task*

- Once the task is configured, click "OK" to save it.
- Right-click on the task in the Task Scheduler Library and select "Export".
- Save the task in :file:`filename.xml` format.

Creating a Package Integrating a Scheduled Task
-----------------------------------------------

*Step 1: Create a Package Template*

- Go to the WAPT console and access the :guilabel:`WAPT Packages` tab.
- Generate a package template by selecting :menuselection:`Make package template from the setup file --> Empty Package`.

*Step 2: Add the XML File*

- Place the exported :file:`filename.xml` file at the root of the package.

*Step 3: Configure the Installation Script*

- Open the :file:`setup.py` file of the package.
- Add the following code, adapting the task name and the XML file name:

.. code-block::

  # -*- coding: utf-8 -*-
  from setuphelpers import *

  def install():
    run('schtasks /CREATE /RU SYSTEM /TN taskname /xml filename.xml')

  def uninstall():
    run('schtasks /DELETE /TN taskname /F')

If you want a task schedule for a wapt package you can go to :ref:`Triggering the WAPT Exit utility with a scheduled task <schedule_task_waptexit>` for more details.

.. _meta-package:

Infrastructure as Code with Meta-Packages
=========================================

Implementing :term:`Infrastructure as Code (IaC)` allows you to manage and provision your infrastructure through code, rather than through manual processes. One effective way to achieve this is by using **meta-packages**. A meta-package is a single package that embeds all necessary components and performs actions based on a script. This approach streamlines the deployment and management of software across various machines.

Examples of Meta-Packages:

    *Installing Software Based on IP Address:*
    You can target all machines with an IP address starting with 10.10 to install specific software.

    *Targeting Machines in an Organizational Unit (OU):*
    You can focus on all machines within a particular OU to install certain packages. For example, you might want to install specific software on all Debian machines within a particular OU.

Script Examples:

Example 1: Installing Based on Machine Chassis Type

.. code-block::
  
  def install():
    chassis_types = run_powershell('(Get-WmiObject -Class Win32_SystemEnclosure).ChassisTypes')
    if type(chassis_types) != list:
        chassis_types = [chassis_types]
    nomads_types = [8, 9, 10, 12, 14, 18, 21, 24, 30, 31, 32]
    if any(chassis_type in nomads_types for chassis_type in chassis_types):
        print("Installing nomads configuration")
        WAPT.install("tis-nomads-wua-conf")
    else:
        print("Installing desktop configuration")
        WAPT.install("tis-desktop-wua-conf")


Example 2: Excluding Specific Machines from Installation

.. code-block::
  
  # -*- coding: utf-8 -*-
  from setuphelpers import *

  def install():
      # Do not install the package tis-package on the following machines
      excluded_machines = ['machine25.mydomain.lan', 'machine36.mydomain.lan']
      if not get_fqdn() in excluded_machines:
          WAPT.install('tis-package')

Example 3: Installing Based on Platform

.. code-block::
  
  if host_info()['platform'] == 'Windows':
    WAPT.install('tis-package-for-windows')

Example 4 : Conditions for installing a CAB based on Windows versions.

.. code-block::

  from setuphelpers import *

  def install():
  #Installation of the KB5062552 if I am on a Windows 11 22H2 below (10.0.22621.5624) or on a Windows 11 23H2 below (10.0.22631.5624).
  version_de_mon_windows = windows_version()
  if version_de_mon_windows > WindowsVersions.Windows11 :

      #Retrieve the pretty version of the Windows version.
      pretty_name = host_info()['windows_version_prettyname']

      #Construction of the expected version based on the Windows version (name).
      dict_version_pretty_name = {'22H2':"10.0.22621.5624",'23h2': "10.0.22631.5624"}

      #If we are on Windows 22H2 or 23H2.
      if pretty_name in dict_version_pretty_name:

          #calculate the expected Windows version.
          expected_version = dict_version_pretty_name[pretty_name]

          #If we are not on the expected version, then we install the KB5062552 package.
          if Version(version_de_mon_windows) < Version(expected_version):
              WAPT.install('mi-kb5062552')

Example 5 : Install 7zip if a vulnerable version is present in the software inventory.

.. code-block::

  from setuphelpers import *

  def install():
    #Install 7zip if 7zip is already installed and its version is below 24.07.
    need_install_7zip = False
    for u in installed_softwares('7-zip'):
      if Version(u['version']) < Version('24.07'):
        need_install_7zip = True

    if need_install_7zip:
      WAPT.install('tis-7zip')

By using meta-packages and scripting, you can automate and streamline the deployment of software across your infrastructure, making it more efficient and less prone to errors.