.. 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 :keywords: working, WAPT, personalizing, creating packages, documentation .. _creating_WAPT_packages: To create and customize WAPT packages, follow that documentation and you will quickly become a master of WAPT. .. include:: wapt-package-structure.rst ******************** Python 2 to Python 3 ******************** .. _python2to3: .. attention:: With WAPT 2.0, the WAPT internals have switched to python3. WAPT packages must also follow the new python3 syntax. .. list-table:: The principal syntax differences :header-rows: 1 :widths: 80, 10, 10 * - Syntax - Python 2 - Python 3 * - print - :file:`print'Hello'` - :file:`print('Hello')` * - unicode string - :file:`ur` - :file:`r` * - operators - :file:`<> <=> !=` - :file:`!=` * - Windows registry access - :file:`_winreg` - :file:`winreg` .. hint:: For more details, visit: * https://python-future.org/compatible_idioms.html. * https://blog.couchbase.com/tips-and-tricks-for-upgrading-from-python-2-to-python-3/. .. _envdev_setup: ***************************************************** Setting up your WAPT development and test environment ***************************************************** Prerequisites ============= .. attention:: * It is **required** to be a :term:`Local Administrator` of the machine to use WAPT's integrated environment for developing WAPT packages. * We advise you to create/ edit packages in a fully controlled environment that is safe and *disposable*. * The usage of a disposable virtual machine (like Virtualbox) is recommended. * Import the *tis-pyscripter* package in your local repository and install it on your development computer. Recommendations regarding the test environment ============================================== The recommended method to correctly test your WAPT packages is to use a representative sample of machines in your inventory. So the more heterogeneous your installed base of machines, the larger your sample must be. This method consists of testing the installation of the package on as many platforms and configurations as possible, so to improve its reliability, before the WAPT package is transferred to production repositories. Testing method ============== Operating systems and architectures ----------------------------------- * Windows XP; * Windows 7; * Windows 10; * Windows Server 2008 R2; * Windows Server 2012 and R2; * x86; * x64; * Physical and virtual machines; * laptops; .. hint:: When possible, RC and Beta version of Operating Systems should be tested. State of Windows Updates ------------------------ * **Microsoft Windows host without any Windows update installed**: the objective is to detect Windows updates that are required for the software to work properly and to adapt the WAPT package accordingly; * **Microsoft Windows host with all the latest Windows updates**: the objective is to detect Windows updates that break the package and to adapt the WAPT package accordingly; State of software installations ------------------------------- * **Hosts with many installed packages**: the objective is to detect a possible dependency with an existing application; * **Hosts with many installed packages**: the objective is to detect a possible conflict with an existing application; * **Install older versions of the software**: it is possible that the software installer does not support uninstalling a previous version of the software, in this case, the WAPT package will have to remove older versions of the software before installing the new version; .. _create_package_from_console: ************************************************************* Principles of creating package template from the WAPT console ************************************************************* .. hint:: To create WAPT packages directly from the WAPT console, it is necessary to have installed the WAPT development environment *tis-pyscripter*. Creating a package template from the WAPT console ================================================= In that example, we use the 7-zip MSI setup downloaded from the 7-zip official website. * `Download 7-zip MSI x64 `_. * Create a WAPT package Template from the installer. In the WAPT console, click on :menuselection:`Tools --> Make package template from setup file`: .. figure:: wapt-resources/tools_make_package_template.png :align: center :alt: PyScripter - WAPT console window for creating a package template PyScripter - WAPT console window for creating a package template Select the downloaded MSI setup file and fill in the required fields. Verify that the package name does not contains any version number. .. figure:: wapt-resources/package_wizard.png :align: center :alt: Informations required for creating the package Informations required for creating the package * Two solutions are available: * Click on :guilabel:`Make and edit ....` (recommended) to verify the WAPT package and customize it to your Organization's specific needs. * Click on :guilabel:`Build and upload` to directly build and upload the package into your private repository. .. attention:: The button :guilabel:`Build and upload` directly uploads the package into the private repository without testing. This method works relatively well with MSI installers because their installation is more standardized. However, the first method that consists of first testing locally the package before uploading is the recommended method. .. note:: :ref:`An old command line method is also available `. Customizing the package before uploading it to your repository ============================================================== Before uploading a package to your WAPT repository, you may choose to customize its behavior to your Organization's needs by editing it with :program:`PyScripter`. When creating the package template, click on :guilabel:`Make and edit ....`. .. figure:: wapt-resources/package_wizard_ok.png :align: center :alt: PyScripter - Informations required for creating the package PyScripter - Informations required for creating the package .. figure:: wapt-resources/package_ok_message.png :align: center :alt: PyScripter - The package has been created PyScripter - The package has been created The :program:`PyScripter` IDE allows to edit files in the WAPT package. .. figure:: wapt-resources/modele_package_pyscripter.png :align: center :alt: PyScripter - Customizing a package with PyScripter PyScripter - Customizing a package with PyScripter ************************** Presentation of PyScripter ************************** PyScripter project explorer =========================== .. figure:: wapt-resources/project_explorer.png :align: center :alt: PyScripter - project explorer PyScripter - project explorer The PyScripter project explorer lists the different files that you might need, notably the :file:`control` file and the :file:`setup.py` file. Run Configurations ================== .. figure:: wapt-resources/run_configuration.png :align: center :alt: PyScripter - Run commands in the PyScripter project explorer PyScripter - Run commands in the PyScripter project explorer The :command:`Run` option in the project explorer of:program:`PyScripter` will allow you to launch actions on the packages that you are editing. Editor panel ============ .. figure:: wapt-resources/zone_editeur.png :align: center :alt: PyScripter - Editor panel PyScripter - Editor panel The edition panel in :program:`PyScripter` allows to edit the :file:`setup.py` file and the :file:`control` file. Python Console ============== .. figure:: wapt-resources/console_python.png :align: center :alt: PyScripter - Python console in PyScripter PyScripter - Python console in PyScripter This is the python console visible in :program:`PyScripter`, it will allow you to display the python output when you execute :command:`Run` commands. You can also use it to test/ debug portions of your script :file:`setup.py`. To learn more about the composition of a WAPT package, visit the documentation on the :ref:`structure of a WAPT package `. Testing locally the installation of the WAPT package ==================================================== You can then test the launch of an installation on your development station. .. figure:: wapt-resources/run_install.png :align: center :alt: Running an install command from the PyScripter console Running an install command from the PyScripter console The PyScripter Console allows you to check whether the installation went well. Testing locally the uninstallation of the WAPT package ====================================================== You can then test the launch of an uninstallation on your development station. .. figure:: wapt-resources/remove_from_pyscripter.png :align: center :alt: Running a remove command from the PyScripter console Running a remove command from the PyScripter console The PyScripter Console allows you to check whether the uninstallation went well. ********************************* Packaging .msi packages (example) ********************************* For this example we will take :program:`tightvnc`. You can download it here: https://www.tightvnc.com/download.php Now, you can then generate your package template, please refer to the :ref:`documentation for creating packages from the WAPT console `. Edit the :file:`control` file (``architecture``, ``impacted_process``, ``target_os``, ``description``, ``maintainer`` ...). For more information, visit the :ref:`documentation on the control file structure `. Your :program:`PyScripter` opens, go to your :file:`setup.py`: .. code-block:: python # -*- coding: utf-8 -*- from setuphelpers import * uninstallkey = [] def install(): print('installing tis-tightvnc') install_msi_if_needed('tightvnc-2.8.5-setup-64bit.msi') * The function will test whether a version of the software is already installed on the host using the *uninstall key*. * If the *uninstall key* is already present, the new version of the software will be installed only if the installed version is older. * After the installation, the function will finally test that the *uninstall key* is present and its version, to ascertain that all went well. .. list-table:: List of arguments available with *install_exe_if_needed* :header-rows: 1 :widths: 30, 20, 50 * - Settings - Default value - Description * - ``msi`` - .. - name of the :mimetype:`.msi` file to execute. * - ``min_version`` - None - minimal version above which the software will update. * - ``killbefore`` - [] - list of programs to kill before installing the package. * - ``accept_returncodes`` - [0,3010] - accepted codes other than 0 and 3010 returned by the function. * - ``timeout`` - 300 - maximum installation wait time (in seconds). * - ``properties`` - {} - additional properties to pass as arguments to MSI setup file. * - ``get_version`` - None - value passed as parameter to control the version number instead of the value returned by the *installed_softwares* function * - ``remove_old_version`` - False - automatically removes an older version of a software whose *uninstall key* is identical * - ``force`` - False - forces the installation of the software even though the same *uninstall key* has been found .. note:: The :command:`install_msi_if_needed` method searches for an *uninstall key* in the MSI file properties, it is not necessary to fill it manually in the :file:`setup.py` file. .. hint:: You also don't have to fill in ``killbefore`` if the value specified in the ``impacted_process`` field of the :file:`control` file is correct. .. note:: The :file:`setup.py` could have looked like this, but the method is less elegant because it does less checking: .. code-block:: python # -*- coding: utf-8 -*- from setuphelpers import * uninstallkey = ["{8B9896FC-B4F2-44CD-8B6E-78A0B1851B59}"] def install(): print('installing tis-tightvnc') run('msiexec /norestart /q /i "tightvnc-2.8.5-setup-64bit.msi"') Run the installation and see what happens when the software is already installed. .. code-block:: bash wapt-get -ldebug install C:\waptdev\tis-tightvnc-wapt Installing WAPT file C:\waptdev\tis-tightvnc-wapt MSI tightvnc-2.8.5-gpl-setup-64bit.msi already installed. Skipping msiexec Results: === install packages === C:\waptdev\tis-tightvnc-wapt | tis-tightvnc (2.8.5.0-1) Passing additional arguments ============================ To pass additional arguments, store them in a *dict*. .. code-block:: python # -*- coding: utf-8 -*- from setuphelpers import * uninstallkey = [] properties = { 'SERVER_REGISTER_AS_SERVICE':0, 'SERVER_ADD_FIREWALL_EXCEPTION':0, } def install(): print(u'Installation en cours de TightVNC') install_msi_if_needed('tightvnc-2.8.5-setup-64bit.msi', properties = properties ) .. note:: The :file:`setup.py` could have looked like this, but the method is less elegant because it does less checking: .. code-block:: python # -*- coding: utf-8 -*- from setuphelpers import * uninstallkey = ["{8B9896FC-B4F2-44CD-8B6E-78A0B1851B59}"] def install(): print('installing tis-tightvnc') run('msiexec /norestart /q /i "tightvnc-2.8.5-setup-64bit.msi" SERVER_REGISTER_AS_SERVICE=0 SERVER_ADD_FIREWALL_EXCEPTION=0') Video demonstration =================== .. youtube:: Z6wr6emPGCU .. _simple_exe_packaging: ********************************* Packaging .exe packages (example) ********************************* * Download the :mimetype:`.exe` installer from a reliable source. Download the installer in exe format Firefox ESR x64 on https://download.mozilla.org/?product=firefox-esr-latest-ssl&os=win64 * Look up documentation relating to silent flags: * On the `Official Mozilla website `_. * Other methods for finding information on silent flags: * `WPKG packages repository `_. * `Chocolatey packages repository `_. * Search on the Internet with the search terms: *Firefox silent install*. * Then generate your package template, please refer to the :ref:`documentation for creating packages from the WAPT console `. :program:`PyScripter` loads up and opens the :mimetype:`.exe` package project. .. figure:: wapt-resources/pyscripter_firefox_esr.png :align: center :alt: PyScripter opening with focus on the *control* file PyScripter opening with focus on the *control* file * Edit the :file:`control` file (``architecture``, ``impacted_process``, ``target_os``, ``description``, ``maintainer`` ...). For more information, visit the :ref:`documentation on the control file structure `. * Check the :file:`control` file content. Mozilla Firefox-ESR does not comply to industry standards and returns an erroneous version number (it appears to be the installer packaging software version number). * Original :file:`\control` file. .. literalinclude:: wapt-resources/package-exe-control_origin.txt :emphasize-lines: 2 * Modified :file:`control` file. .. literalinclude:: wapt-resources/package-exe-control_modified.txt :emphasize-lines: 2,6,7,8 .. note:: A sub-version *-1* has been appended to the software version number; it is the packaging version of the WAPT package. It allows the Package Developer to release several WAPT package versions of the same software, very useful for very rapid and iterative development. Using *install_exe_if_needed* The function is slightly the same as that used with :mimetype:`.msi` installers, with some differences: * The function requires to pass the silent flag as an argument. * The function requires to pass the *uninstall key* as an argument. .. code-block:: python # -*- coding: utf-8 -*- from setuphelpers import * uninstallkey = [] def install(): print('installing tis-firefox-esr') install_exe_if_needed("Firefox Setup 45.5.0esr.exe",silentflags="-ms",key='',min_version="4.42.0.0") .. list-table:: List of arguments available with *install_exe_if_needed* :header-rows: 1 :widths: 30, 20, 50 * - Settings - Default value - Description * - ``exe`` - .. - Name of the :mimetype:`.exe` file to execute. * - ``silentflags`` - .. - Silent parameters to pass as arguments to the installer. * - ``key`` - None - Software *uninstall key*. * - ``min_version`` - None - Minimal version above which the software will update. * - ``killbefore`` - [ ] - List of programs to kill before installing the package. * - ``accept_returncodes`` - [0,3010] - Accepted codes other than 0 and 3010 returned by the function. * - ``timeout`` - 300 - Maximum installation wait time (in seconds). * - ``get_version`` - None - Value passed as parameter to control the version number instead of the value returned by the :command:`installed_softwares` function. * - ``remove_old_version`` - False - Automatically removes an older version of a software whose uninstallkey is identical * - ``force`` - False - Forces the installation of the software even though the same uninstallkey has been found The package will then have this behavior: * Firefox will install only if Firefox is not yet installed or if the installed version of Firefox is less than 45.5.0, unless the ``--force`` option is passed as argument when installing the package. * On installing, the running :program:`firefox.exe` processes will be killed (with the value indicated in ``impacted_process`` of the :file:`control` file). * The function will add by itself the *uninstall key*, so leave the *uninstall key* argument empty. * When finishing the installation of the package, the function will check that the *uninstall key* is present on the machine and that the version of Firefox is greater than 45.5.0; if this not the case, the package will be flagged as **ERROR**. Finding the uninstallation key ============================== Unlike :mimetype:`.msi` files, the key to uninstall an :mimetype:`.exe` is not in the file properties. So you need to install the software first to know the uninstall key. Therefore you must start once the installation from :program:`pyscripter` with the :guilabel:`run configuration` and then :guilabel:`install`. .. figure:: wapt-resources/run_install.png :align: center Once the software is installed, go to the WAPT console, then find your development machine. In the software inventory tab find your software and copy the value indicated in the uninstallkey column. .. figure:: wapt-resources/uninstallkey_from_console.png :align: center :alt: Retrieving an uninstallkey from the console Retrieving an uninstallkey from the console You must also check the value of the version with the value indicated in ``min_version`` in your :file:`setup.py`. Modify your :file:`setup.py` with the new parameters: .. code-block:: python # -*- coding: utf-8 -*- from setuphelpers import * uninstallkey = [] def install(): print('installing tis-firefox-esr') 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") To test that your key works correctly, you must run an installation again in :program:`pyscripter`. .. figure:: wapt-resources/run_install.png :align: center WAPT should not attempt to install the software because it is already present, the following message should display: .. code-block:: python :emphasize-lines: 6 >>> *** Remote Interpreter Reinitialized *** Command Line : install "c:\waptdev\tis-firefox-esr_x64_PROD_fr-wapt\WAPT\.." Using config file: C:\Program Files (x86)\wapt\wapt-get.ini Installing WAPT files c:\waptdev\tis-firefox-esr_x64_PROD_fr-wapt Exe setup Firefox_Setup_78.7.1esr.exe already installed. Skipping Results: === install packages === c:\waptdev\tis-firefox-esr_x64_PROD_fr-wapt | tis-firefox-esr (78.7.1-102) Now you can now test the uninstallation: .. figure:: wapt-resources/remove_from_pyscripter.png :align: center :alt: Testing the uninstallation Testing the uninstallation You can now build and upload your package, please refer to the :ref:`documentation for build and upload packages from the WAPT console `. .. note:: If you leave the uninstallkey blank, uninstalling your package will not work. Special case of a non-silent uninstaller ======================================== In some particular cases, a package using :command:`install_exe_if_needed` fills in the *uninstall key*, but the *uninstall key* points to a non silent uninstaller. We have to circumvent that problem by using a function that will remove the *uninstall key* at the end of the installation. .. code-block:: python :emphasize-lines: 13 # -*- coding: utf-8 -*- from setuphelpers import * uninstallkey = [] def install(): install_exe_if_needed("setup.exe", silentflags="/s", key='{D9E87643-0005-447E-9111-78697A9C1595}', min_version="14.0") uninstallkey.remove('{D9E87643-0005-447E-9111-78697A9C1595') def uninstall(): run(r'"C:\Program Files\Kutl\uninstall.exe" /supersilent') .. hint:: The uninstall feature can also be used to run code in addition to uninstalling software, ex: delete folder, delete shortcut ... Video demonstration =================== .. youtube:: z_EN2CBCTcY .. _packaging_empty_packages: ************************ Packaging empty packages ************************ .. _build_upload_from_console: ****************************************************** Building the package and sending it to the WAPT server ****************************************************** * Once the package is ready, build it and send it to the WAPT server. .. figure:: wapt-resources/build-upload-console.PNG :align: center :alt: Option "build-upload in waptconsole" Option "Build and upload package" in waptconsole * Select the package in the :file:`c:\\waptdev` folder. .. figure:: wapt-resources/build-upload-console-select-folder.PNG :align: center * Confirm the selected package. .. figure:: wapt-resources/build-upload-console-confirm.PNG :align: center You have just uploaded your first wapt package. .. note:: :ref:`An old command line method is available here `. .. warning:: Once your package has uploaded, refresh the package list using the :guilabel:`Refresh packages list` button or by pressing :kbd:`F5` on your keyboard. .. _returncodes: Working with non standard return codes ====================================== Return codes are used to feed back information on whether a software has installed correctly. In Windows, the standard successful return code is [0]. If you know that your WAPT packages installs correctly, yet you still get a return code other than [0], then you can explicitly tell WAPT to ignore the error code by using the parameter ``accept_returncodes``. You can find out how to use the ``accept_returncodes`` parameter by exploring this package code. .. code-block:: python :emphasize-lines: 30 # -*- coding: utf-8 -*- from setuphelpers import * import re uninstallkey = [] def is_kb_installed(hotfixid): installed_update = installed_windows_updates() if [kb for kb in installed_update if kb['HotFixID' ].upper() == hotfixid.upper()]: return True return False def waiting_for_reboot(): # Query WUAU from the registry if reg_key_exists(HKEY_LOCAL_MACHINE,r"SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired") or \ reg_key_exists(HKEY_LOCAL_MACHINE,r"SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending") or \ reg_key_exists(HKEY_LOCAL_MACHINE,r'SOFTWARE\Microsoft\Updates\UpdateExeVolatile'): return True return False def install(): kb_files = [ 'windows10.0-kb4522355-x64_af588d16a8fbb572b70c3b3bb34edee42d6a460b.msu', ] with EnsureWUAUServRunning(): for kb_file in kb_files: kb_guess = re.findall(r'^.*-(KB.*)-',kb_file) if not kb_guess or not is_kb_installed(kb_guess[0]): print('Installing {}'.format(kb_file)) run('wusa.exe "{}" /quiet /norestart'.format(kb_file),accept_returncodes=[0,3010,2359302,-2145124329],timeout=3600) else: print('{} already installed'.format(kb_file)) if waiting_for_reboot(): print('A reboot is needed!') .. hint:: The full list of Windows Installer Error Messages can be found by visiting `this page `_. .. _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` ... .. code-block:: python makepath(programfiles,'Mozilla','Firefox') ... makes the path variable for :file:`C:\\Program Files (x86)\\Mozilla\\Firefox`. .. hint:: For more informations or to learn more on arguments on that function, please visit official Setuphelpers reference documentation: https://www.wapt.fr/en/api-doc-1.5/source/setuphelpers.html?highlight=makepath#setuphelpers.makepath Creating and destroying directories ----------------------------------- Command :command:`mkdirs` ... .. code-block:: python mkdirs('C:\\test') ... creates the directory :file:`C:\\test`. .. hint:: For more informations or to learn more on arguments on that function, please visit official Setuphelpers reference documentation: https://www.wapt.fr/en/api-doc-1.5/source/setuphelpers.html?highlight=mkdirs#setuphelpers.mkdirs Command :command:`remove_tree` ... .. code-block:: python remove_tree(r'C:\tmp\target') ... destroys the directory :file:`C:\\tmp\\target`. .. hint:: For more informations or to learn more on arguments on that function, please visit official Setuphelpers reference documentation: https://www.wapt.fr/en/api-doc-1.5/source/setuphelpers.html?highlight=remove_tree#setuphelpers.remove_tree Checking if a path is a file or a folder ---------------------------------------- Command :command:`isdir` ... .. code-block:: python isdir(makepath(programfiles32,'software')): print('The directory exists') ... checks if :file:`C:\\Program Files (x86)\\software` is a directory. .. hint:: For more informations or to learn more on arguments on that function, please visit official Setuphelpers reference documentation: https://www.wapt.fr/en/api-doc-1.5/source/setuphelpers.html?highlight=isdir#setuphelpers.isdir Command :command:`isfile` ... .. code-block:: python isfile(makepath(programfiles32,'software','file')): print('file exist') ... checks if :file:`C:\\Program Files (x86)\\software\\file` is a file. .. hint:: For more informations or to learn more on arguments on that function,* please visit official Setuphelpers reference documentation: https://www.wapt.fr/en/api-doc-1.5/source/setuphelpers.html?highlight=isfile#setuphelpers.isfile Check if a directory is empty ----------------------------- Command :command:`dir_is_empty` ... .. code-block:: python dir_is_empty(makepath(programfiles32,'software')): print('dir is empty') ... checks that directory :file:`C:\\Program Files (x86)\\software` is empty. .. hint:: For more informations or to learn more on arguments on that function, please visit official Setuphelpers reference documentation: https://www.wapt.fr/en/api-doc-1.5/source/setuphelpers.html?highlight=dir_is_empty#setuphelpers.dir_is_empty Copying a file -------------- Command :command:`filecopyto` ... .. code-block:: python filecopyto('file.txt',makepath(programfiles32,'software')) ... copies :file:`file.txt` into the :file:`C:\\Program Files (x86)\\software` directory. .. hint:: For more informations or to learn more on arguments on that function, please visit official Setuphelpers reference documentation: https://www.wapt.fr/en/api-doc-1.5/source/setuphelpers.html?highlight=filecopyto#setuphelpers.filecopyto Copying a directory ------------------- Command :command:`copytree2` ... .. code-block:: python copytree2('sources','C:\\projet') ... copies the :file:`sources` folder into the :file:`C:\\projet` directory. .. hint:: For more informations or to learn more on arguments on that function, please visit official Setuphelpers reference documentation: https://www.wapt.fr/en/api-doc-1.5/source/setuphelpers.html?highlight=copytree2#setuphelpers.copytree2 Retrieving the version of a file -------------------------------- Command :command:`get_file_properties` ... .. code-block:: python get_file_properties(makepath(programfiles32,'InfraRecorder','infrarecorder.exe'))['ProductVersion'] ... shows package properties. .. hint:: For more informations or to learn more on arguments on that function, please visit official Setuphelpers reference documentation: https://www.wapt.fr/en/api-doc-1.5/source/setuphelpers.html?highlight=get_file_properties#setuphelpers.get_file_properties ************************** Manipulating registry keys ************************** Checking the existence of a registry key ======================================== Command :command:`registry_readstring` ... .. code-block:: python if registry_readstring(HKEY_LOCAL_MACHINE, "SOFTWARE\\Google\\Update\\Clients\\{8A69D345-D564-463c-AFF1-A69D9E530F96}", 'pv'): print('key exist') ... checks if registry key *{8A69D345-D564-463c-AFF1-A69D9E530F96}* exists in registry path :file:`SOFTWARE\\Google\\Update\\Clients` of *HKEY_LOCAL_MACHINE*. .. hint:: For more informations or to learn more on arguments on that function, please visit official Setuphelpers reference documentation: https://www.wapt.fr/en/api-doc-1.5/source/setuphelpers.html?highlight=registry_readstring#setuphelpers.registry_readstring Showing the value of a registry key =================================== Command :command:`registry_readstring` ... .. code-block:: python print(registry_readstring(HKEY_LOCAL_MACHINE, r'SOFTWARE\Google\Update\Clients\{8A69D345-D564-463c-AFF1-A69D9E530F96}', 'pv')) ... reads the value *{8A69D345-D564-463c-AFF1-A69D9E530F96}* stored in the registry path :file:`SOFTWARE\\Google\\Update\\Clients` of *HKEY_LOCAL_MACHINE*. .. hint:: For more informations or to learn more on arguments on that function, please visit official Setuphelpers reference documentation: https://www.wapt.fr/en/api-doc-1.5/source/setuphelpers.html?highlight=registry_readstring#setuphelpers.registry_readstring Modifying the value of a registry key ===================================== Command :command:`registry_setstring` ... .. code-block:: python registry_setstring(HKEY_CURRENT_USER, "SOFTWARE\\Microsoft\\Windows Live\\Common",'TOUVersion','16.0.0.0', type=REG_SZ) ... modifies the value of the registry key *TOUVersion* stored in the registry path :file:`SOFTWARE\\Microsoft\\Windows Live` of *HKEY_CURRENT_USER*. .. hint:: For more informations or to learn more on arguments on that function, please visit official Setuphelpers reference documentation: https://www.wapt.fr/en/api-doc-1.5/source/setuphelpers.html?highlight=registry_setstring#setuphelpers.registry_setstring ********************************* Creating and destroying shortcuts ********************************* With WAPT setuphelper it's possible to create different type of shortcut. A simple shortchut ================== .. todo:: upcoming documentation A menu folder shortcut ====================== .. todo:: upcoming documentation A desktop shortcut ================== .. todo:: upcoming documentation create_desktop_shortcut ----------------------- Command :command:`create_desktop_shortcut` ... .. code-block:: python create_desktop_shortcut(r'WAPT Console Management',target=r'C:\Program Files (x86)\wapt\waptconsole.exe') ... 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. .. hint:: For more informations or to learn more on arguments on that function, please visit official Setuphelpers reference documentation: https://www.wapt.fr/en/api-doc-1.5/source/setuphelpers.html?highlight=create_desktop_shortcut#setuphelpers.create_desktop_shortcut remove_desktop_shortcut ----------------------- Command :command:`remove_desktop_shortcut` ... .. code-block:: python remove_desktop_shortcut('WAPT Console Management') ... deletes the *WAPT Console Management* shortcut from the folder :file:`C:\\Users\\Public`; the shortcut is deleted for all users. .. hint:: For more informations or to learn more on arguments on that function, please visit official Setuphelpers reference documentation: https://www.wapt.fr/en/api-doc-1.5/source/setuphelpers.html?highlight=remove_desktop_shortcut#setuphelpers.remove_desktop_shortcut Firefox places a shortcut on the all users desktop, we are going to delete it. We will use the ``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. A current user desktop shortcut =============================== create_user_desktop_shortcut ---------------------------- .. hint:: These functions are used in session_setup context. Command :command:`create_user_desktop_shortcut` ... .. code-block:: python create_user_desktop_shortcut(r'WAPT Console Management',target=r'C:\Program Files (x86)\wapt\waptconsole.exe') ... creates the shortcut *WAPT Console Management* on user desktop pointing to :file:`C:\\Program Files (x86)\\wapt\\waptconsole.exe`. .. hint:: For more informations or to learn more on arguments on that function, please visit official Setuphelpers reference documentation: https://www.wapt.fr/en/api-doc-1.5/source/setuphelpers.html?highlight=create_user_desktop_shortcut#setuphelpers.create_user_desktop_shortcut remove_user_desktop_shortcut ---------------------------- Command :command:`remove_user_desktop_shortcut` ... .. code-block:: python remove_user_desktop_shortcut('WAPT Console Management') ... deletes the *WAPT Console Management* shortcut from the logged in user's desktop. .. hint:: For more informations or to learn more on arguments on that function, please visit official Setuphelpers reference documentation: https://www.wapt.fr/en/api-doc-1.5/source/setuphelpers.html?highlight=remove_user_desktop_shortcut#setuphelpers.remove_user_desktop_shortcut *************************************** Windows environment/ Software/ Services *************************************** Windows version check ===================== Command :command:`windows_version` ... .. code-block:: python windows_version()`_. Check if 64bits architecture ============================ Command :command:`iswin64` ... .. code-block:: python if iswin64(): print('Pc x64') else: print('Pc not x64') ... checks that the system architecture is 64bits. .. hint:: For more informations or to learn more on arguments on that function, please visit official Setuphelpers reference documentation: https://www.wapt.fr/en/api-doc-1.5/source/setuphelpers.html?highlight=iswin64#setuphelpers.iswin64 Program Files variable ====================== programfiles/ programfiles32/ programfiles64 Return different *ProgramFiles* locations Command :command:`programfiles64` ... .. code-block:: python print(programfiles64()) ... returns native Program Files directory, eg. :file:`C:\\Program Files (x86)` on either win64 or win32 architecture. .. code-block:: python print(programfiles()) ... returns path of the 32bit Program Files directory, eg. :file:`Programs Files (x86)` on win64 architecture, and :file:`Programs Files` on win32 architecture. .. code-block:: python print(programfiles32()) AppData variable ================ user_appdata/ user_local_appdata .. hint:: These functions are used with :command:`session_setup` Command :command:`user_appdata` ... .. code-block:: python print(user_appdata()) ... returns roaming *AppData* profile path of logged on user (:file:`C:\\Users\\%username%\\AppData\\Roaming`). .. hint:: For more informations or to learn more on arguments on that function, please visit official Setuphelpers reference documentation: https://www.wapt.fr/en/api-doc-1.5/source/setuphelpers.html?highlight=user_appdata#setuphelpers.user_appdata Command :command:`user_local_appdata` ... .. code-block:: python print(user_local_appdata()) ... returns the local *AppData* profile path of the logged on user (:file:`C:\\Users\\%username%\\AppData\\Local`). .. hint:: For more informations or to learn more on arguments on that function, please visit official Setuphelpers reference documentation: https://www.wapt.fr/en/api-doc-1.5/source/setuphelpers.html?highlight=user_local_appdata#setuphelpers.user_local_appdata Disable temporarily the wow3264 file redirector =============================================== Command :command:`disable_file_system_redirection` ... .. code-block:: python with disable_file_system_redirection(): filecopyto('file.txt',system32()) .. hint:: For more informations or to learn more on arguments on that function, please visit official Setuphelpers reference documentation: https://www.wapt.fr/en/api-doc-1.5/source/setuphelpers.html?highlight=disable_file_system_redirection#setuphelpers.disable_file_system_redirection Disable wow3264 redirection in the current context Get current user ================ Command :command:`get_current_user` ... .. code-block:: python print(get_current_user()) ... shows the currently logged on username .. hint:: For more informations or to learn more on arguments on that function, please visit official Setuphelpers reference documentation: https://www.wapt.fr/en/api-doc-1.5/source/setuphelpers.html?highlight=get_current_user#setuphelpers.get_current_user Get computer name ================= Command :command:`get_computername` ... .. code-block:: python print(get_computername()) ... shows the name of the computer .. hint:: For more informations or to learn more on arguments on that function, please visit official Setuphelpers reference documentation: https://www.wapt.fr/en/api-doc-1.5/source/setuphelpers.html?highlight=get_computername#setuphelpers.get_computername Get computer domain =================== Command :command:`get_domain_fromregistry` ... .. code-block:: python get_domain_fromregistry() ... returns the :abbr:`FQDN (Fully Qualified Domain Name)` of the computer. .. hint:: For more informations or to learn more on arguments on that function, please visit official Setuphelpers reference documentation: https://www.wapt.fr/en/api-doc-1.5/source/setuphelpers.html?highlight=get_domain_fromregistry#setuphelpers.get_domain_fromregistry Action on installed software ============================ Check installed software ------------------------ Command :command:`installed_softwares` ... .. code-block:: python installed_softwares('winscp') ... returns the list of installed software on the computer from registry in an array. .. 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}] .. hint:: For more informations or to learn more on arguments on that function, please visit official Setuphelpers reference documentation: https://www.wapt.fr/en/api-doc-1.5/source/setuphelpers.html?highlight=installed_softwares#setuphelpers.installed_softwares Get uninstall command with registry ------------------------------------ Command :command:`uninstall_cmd` ... .. code-block:: python uninstall_cmd('winscp3_is1') ... returns the silent uninstall command. .. code-block:: bash "C:\Program Files\WinSCP\unins000.exe" /SILENT .. hint:: For more informations or to learn more on arguments on that function, please visit official Setuphelpers reference documentation: https://www.wapt.fr/en/api-doc-1.5/source/setuphelpers.html?highlight=uninstall_cmd#setuphelpers.uninstall_cmd Uninstalling software --------------------- .. code-block:: python for soft in installed_softwares('winscp3'): if Version(soft['version']) < Version('5.0.2'): run(WAPT.uninstall_cmd(soft['key'])) * For each item of the list return by *installed_softwares* containing keyword *winscp*. * If the version is lower than 5.0.2. * Then uninstall using the *uninstall_cmd* and specifying the corresponding *uninstallkey*. .. hint:: For more informations or to learn more on arguments on that function, please visit official Setuphelpers reference documentation: https://dev.tranquil.it/sphinxdocs/source/setuphelpers.html?highlight=uninstall_cmd#setuphelpers.uninstall_cmd Kill task --------- Command :command:`killalltasks` ... .. code-block:: python killalltasks('firefox') ... kills the process named *Firefox*. .. hint:: For more informations or to learn more on arguments on that function, please visit official Setuphelpers reference documentation: https://www.wapt.fr/en/api-doc-1.5/source/setuphelpers.html?highlight=killalltasks#setuphelpers.killalltasks ************************* Using control file fields ************************* It's possible to use control file informations on :file:`setup.py` Get packages version ==================== .. code-block:: python def setup(): print(control['version']) ... shows the *version* value from the :file:`control` file. .. code-block:: python def setup(): print(control['version'].split('-',1)[0]) ... shows the software version number without the WAPT version number from the :file:`control` file. Get software name ================= .. todo:: upcoming documentation ************************************************* Managing a WAPT package with another WAPT package ************************************************* Installing a package ==================== Command :command:`install` ... .. code-block:: python WAPT.install('tis-scratch') ... installs *tis-scratch* on the computer. Removing a package ================== Command :command:`remove` ... .. code-block:: python WAPT.remove('tis-scratch') ... uninstalls *tis-scratch* from the computer. Forgetting a package ==================== Command :command:`forget_packages` ... .. code-block:: python WAPT.forget_packages('tis-scratch') ... informs WAPT to forget *tis-scratch* 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 (:code:`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 this extension: https://addons.mozilla.org/fr/firefox/addon/enterprise-policy-generator/. 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 machine to check if a non-esr version of :program:`Firefox` is installed. To reproduce our example, download and install the consumer version here: 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 ``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 :command:`session_setup` function benefits from the power of python to achieve a high level of automation. Principles of *session_setup* ----------------------------- The WAPT :command:`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 :command:`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\\waptdb.sqlite`) the instruction sets of all WAPT packages. .. attention:: :command:`session_setup` 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 :command:`session_setup` that have been already been played. Output example of :code:`wapt-get session-setup ALL`: .. note:: the connected user's ``session_setup`` had already 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 :command:`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/run-session-setup.png :align: center :alt: PyScripter - running session-setup PyScripter - running session-setup * Finally, check that the icon is present on the desktop. .. _auditing_packages_for_compliance: Using the audit functions for compliance ======================================== .. note:: This feature is available in the **Enterprise** version. 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 machines 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 :command:`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 :command:`audit` tasks are launched once after every :command:`upgrade`, then regularly as defined by the ``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 ``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 :command:`audit` script is defined in the package's :file:`setup.py` with a function :command:`def audit()`: In this example, we are improving the Firefox package previously studied in this documentation. * Add the ``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`. .. figure:: wapt-resources/run_audit.png :align: center :alt: PyScripter - Running an audit 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 console (Click on the package then on the audit column): .. figure:: wapt-resources/audit_in_console.png :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 :command:`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 first copy the file(s) in a temporary folder during package installation. Planning an audit ----------------- The :command:`audit` tasks are launched once after every :command:`upgrade`, then regularly as defined with the ``audit_schedule`` value. The value is contained in the control file of your package. By default, if :command:`audit_schedule` is empty, the audit task will need to be launched manually or from the WAPT console. 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. .. _def_update: Updating automatically a software package ========================================= .. note:: This part of the documentation is for advanced users of WAPT. *update_package* functions are very practical, they allow to gain a lot of time when it is time 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/run-update-package.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 `_. .. _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's 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 `. 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 `; 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 `_. * Create a WAPT package template from the downloaded :mimetype:`.msu` file. In the WAPT console, click on :menuselection:`Tools --> Package Wizard`. .. figure:: wapt-resources/tools_make_package_template.png :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/package_wizard_msu.png :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 .. warning:: **Do not launch this command as root or with a sudo.** When you create a template, there will be several files in the folder :file:`.vscode` inside your 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 packages are stored in linuxuser's home (home of the currently logged in user). VSCode loads up and opens package project. .. figure:: wapt-resources/vscode_vlc.png :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 **description** to you package, give the **os_target** and the **version** of your package. .. hint:: ``os_target`` for unix is *linux* .. warning:: The software ``version`` number in your :file:`control` file must start at 0, and not the version number of the software, as we don't know precisely from apt/yum repo which version number it will be. * 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 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/remove_package-linux.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 ``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/remove_package-linux.png :align: center :alt: After uninstallation, the software is correctly removed After uninstallation, the software is correctly removed Building and uploading the package ---------------------------------- You will find your package here: :file:`~/waptdev`. You need to transfer the package folder to the Windows machine that has the private key. Then, please refer to the :ref:`documentation for building and uploading packages from the WAPT console `. .. _encryting_sensitive_data_in_package: Encrypting sensitive data contained in a WAPT package ===================================================== .. note:: This part of the documentation is for advanced users of WAPT. This feature is available only in the **Enterprise** version. 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 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 :term:`Local Administrators`. We will therefore encrypt the sensitive data contained inside the package with the certificate belonging to the machine. 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 machine and then encrypt the sensitive text with it. The encrypted text for each machine is then stored in a :file:`encrypt-txt.json` file at the root of the package. During the installation of the package, the WAPT agent will take the encrypted text and decrypt it with its private key. .. code-block:: python # -*- coding: utf-8 -*- from setuphelpers import * import json from waptcrypto import SSLCertificate import waptguihelper import base64 def install(): encryptlist = json.loads(open('encrypt-txt.json','r').read()) if WAPT.host_uuid in encryptlist: host_key = WAPT.get_host_key() v = base64.b64decode(encryptlist[WAPT.host_uuid]) encrypttxt = host_key.decrypt(v).decode('utf-8') print(r"Here is the deciphered text: %s" % encrypttxt) else: error("%s not found in encrypt-txt.json" % WAPT.host_uuid) def update_package(): urlserver = inifile_readstring(makepath(install_location('WAPT_is1'),'wapt-get.ini'),'global','wapt_server').replace('https://','') encrypttxt = input("Enter the text to be encrypted: ") encryptlist = {} credentials_url = waptguihelper.login_password_dialog('Credentials for wapt server',urlserver,'admin','') data = json.loads(wgets('https://%s:%s@%s/api/v1/hosts?columns=host_certificate&limit=10000' % (credentials_url['user'],credentials_url['password'],urlserver))) for value in data['result']: if value['host_certificate']: host_cert = SSLCertificate(crt_string = value['host_certificate']) encryptlist[value['uuid']] = base64.b64encode(host_cert.encrypt(encrypttxt.encode('utf-8'))).decode('utf-8') print(value['computer_fqdn'] + ':' + value['uuid'] + ':' + encryptlist[value['uuid']]) open('encrypt-txt.json','w').write(json.dumps(encryptlist)) .. attention:: The python output (log install of the package) is readable by the users on the machine, so **you should not display the decrypted text with a print during installation**. .. _using_different_development_environments: ************************************************* Using different IDEs for developing WAPT packages ************************************************* If you are used to work with another :term:`IDE`, you can be relieved now as WAPT supports other editors. Some code editors are natively supported: * PyScripter; * VSCode; * VSCodium; Other editors can be selected and will be launched when you create a new template for a WAPT package from WAPT Console. .. note:: Using a supported IDE will launch the WAPT package project with a valid debug configuration. On Windows ========== To configure another editor for WAPT, you must modify the ``editor_for_packages`` attribute in the ``[global]`` section of your WAPT console's :file:`%LOCALAPPDATA%\\waptconsole\\waptconsole.ini` configuration file. .. list-table:: Alternative methods for editing WAPT packages on Windows :header-rows: 1 :widths: 50 50 * - Editor name - ``editor_for_packages`` value * - PyScripter - None * - Microsoft Visual Studio Code - :program:`vscode` or :program:`code` * - Microsoft Visual Studio Codium - :program:`vscodium` or :program:`codium` Example config in :file:`waptconsole.ini`: .. code-block:: ini [global] ... editor_for_packages=vscode On Linux / macOS ================ To configure another editor for WAPT, you must modify the ``editor_for_packages`` attribute in the ``[global]`` section of your WAPT agent configuration file: :file:`/opt/wapt/wapt-get.ini`. By default, if the ``editor_for_packages`` attribute is empty, WAPT will try to launch (in that order): * :program:`vscodium`; * :program:`vscode`; * :program:`nano`; * :program:`vim`; * :program:`vi`. .. list-table:: Alternative methods for editing WAPT packages on Linux :header-rows: 1 :widths: 50 50 * - Editor name - ``editor_for_packages`` value * - Microsoft Visual Studio Code - :program:`vscode` or :program:`code` * - Microsoft Visual Studio Codium - :program:`vscodium` or :program:`codium` * - Nano - :program:`nano` * - Vim - :program:`vim` * - Vi - :program:`vi` .. code-block:: ini [global] ... editor_for_packages=vim *************************************** Configuring WAPT to use a custom editor *************************************** On Windows ========== Custom editors can be used, for example :program:`Notepad++` or :program:`PyCharm` on Windows. .. list-table:: Using a custom text editor on Windows :header-rows: 1 :widths: 50 50 * - Editor name - ``editor_for_packages`` value * - Notepad++ - :file:`C:\\Program Files\\Notepad++\\notepad++.exe {setup_filename}` * - PyCharm - :file:`C:\\Program Files\\JetBrains\\PyCharm Community Edition 2019.3.2\\bin\\pycharm64.exe {wapt_sources_dir}` .. code-block:: ini [global] ... editor_for_packages=C:\Program Files\Notepad++\notepad++.exe {setup_filename} On Linux/ macOS =============== Custom editors can be used, for example :program:`PyCharm`. .. list-table:: Using a custom text editor on linux / macos :header-rows: 1 :widths: 50 50 * - Editor name - ``editor_for_packages`` value * - PyCharm - :file:`/opt/pycharm/bin/pycharm_x64 {wapt_sources_dir}` .. code-block:: ini [global] ... editor_for_packages=/opt/pycharm/bin/pycharm_x64 {wapt_sources_dir} Custom arguments ---------------- .. list-table:: Arguments for the :code:`editor_for_packages` option :header-rows: 1 :widths: 50 50 * - Argument - Description * - :code:`{setup_filename}` - Launches custom editor and edit WAPT package setup.py file * - :code:`{control_filename}` - Launches custom editor and edit WAPT package control file * - :code:`{wapt_sources_dir}` - Launches custom editor and opens WAPT package folder * - :code:`{wapt_base_dir}` - Launches custom editor and opens WAPT install folder