psytricks

PSytricks

PyPI PyPI - License pdoc black

PowerShell Python Citrix Tricks - pun intended.

logo

This package provides an abstraction layer allowing Python code to interact with a Citrix Virtual Apps and Desktops (CVAD) stack, i.e. to fetch status information and trigger actions on machines and sessions. Since CVAD only provides a PowerShell Snap-In to do so, a core component written in Windows PowerShell (note: not PowerShell Core as snap-ins are not supported there) is required.

PSyTricks ships with two options for the PowerShell layer:

  • A wrapper script that is launched as a suprocess from the Python code. It doesn't require any further setup beyond the package installation but performance, well, slow.
  • A (zero-authentication) REST (see the note below on this) service providing several GET and POST endpoints to request status information or perform actions. Performance is much better compared to the wrapper script, but obviously this requires the code to be running as a service in an appropriate permission context.

NOTE: this RESTful claim is actually not entirely true. Or basically not at all, it would be better called an HTTP-JSON-RPC-API. We'll still be using the term REST for it as this is basically what people nowadays think of when they are coming across this label. Sorry, Roy T. Fielding.

๐Ÿคฏ Are you serious?

Calling PowerShell as a subprocess from within Python? ๐Ÿ˜ณ

To convert results to JSON and pass them back, just to parse it again in Python. Really? ๐Ÿง

Or, not sure if that's any better, implementing an HTTP REST API in plain PowerShell?!? ๐Ÿซฃ

โœ… Yes. We. Are

And the package name was chosen to reflect this.

To be very clear: performance of the wrapper script is abysmal, but this is not at all an issue for us. Abysmal, as in: for every wrapped call a full (new) PowerShell process needs to be instantiated, usually taking something like 1-2 seconds. โณ

The REST interface provides a much better performance, at the cost of some additional setup. If you're happy to take on this approach, the package offers a very smooth ride. ๐ŸŽข๐ŸŽก

๐Ÿ› ๐Ÿšง Installation

Prerequisites

As mentioned above, the Citrix Broker PowerShell Snap-In is required to be installed on the machine that will run the wrapper script, since its commands are being used to communicate with the CVAD stack. This is also the reason why this package will work on Windows PowerShell only as snap-ins are not supported on other PowerShell editions. Please note this also implies that the latest usable PowerShell version is 5.1 as newer ones have dropped support for snap-ins (but that's a different problem that Citrix will have to solve at some point).

To install the snap-in, look for an MSI package like this in the Delivery Controller or XenDesktop installation media and install it as usual:

  • Broker_PowerShellSnapIn_x64.msi

Installing the ๐Ÿ package

In case you're planning to use psytricks via the subprocess approach (discouraged but less components to set up), you will have to install the package itself on the Windows machine having the above mentioned Snap-In installed. For the REST approach (recommended) only the PowerShell service described in the section below has to run on that machine - the Python package can be installed on any computer that is able to talk to the REST service.

For installing psytricks please create and activate a venv, then run:

pip install psytricks

NOTE: this will also register the psytricks CLI tool although that one is mostly meant for testing and demonstration purposes, otherwise the *-Broker* commands provided by the PowerShell snap-in could be used directly.

Setting up the REST service

The easiest way for installing the REST service is to use WinSW (Windows Service Wrapper) but you may choose anything you like to launch the server process like NSSM, Scheduled Tasks ๐Ÿ“…, homegrown dark magic ๐Ÿช„๐Ÿ”ฎ or others.

To go with WinSW simply download the bundled version provided with each PSyTricks release. Just look for the .zip asset having REST and WinSW in its name.

Unzip the downloaded file to the desired target location, e.g. %PROGRAMDATA%\PSyTricks, then copy / rename restricks-server.example.xml to restricks-server.xml and open it in an editor.

Adapt the entries in the <serviceaccount> section to match your requirements and make sure to update the hostname passed via the -AdminAddress parameter in the <startarguments> section. It needs to point to your Citrix Delivery Controller, just in case that's not obvious.

Next step is to install and start the service:

cd C:\ProgramData\PSyTricks
restricks-server.exe isntall
Start-Service RESTricksServer

In case the service doesn't start up, check the Windows Event Log and the .log files created by WinSW in the service directory.

Once the service has started, you can monitor its actions by live-watching the log file:

Get-Content -Wait C:\ProgramData\PSyTricks\restricks-server.log

Tada! That's it, the service is now ready to take HTTP requests (from localhost)! ๐ŸŽ‰

Please be aware that the REST interface doesn't do any authentication on purpose, meaning everything / everyone that can access it will be able to run all requests! We're using it in combination with an SSH tunnel but essentially anything that controls who / what can access the service will do the job.

๐ŸŽช What does it provide?

To interact with CVAD, a wrapper object needs to be instantiated and instructed how to communicate with the stack.

After setting up the REST service as described above and making sure to be able to connect to it (firewall rules, ssh tunnel, ...), a psytricks.wrapper.ResTricksWrapper object can be used while passing the URL under which the REST service is reachable, e.g.

from psytricks.wrapper import ResTricksWrapper

wrapper = ResTricksWrapper(base_url="http://localhost:8080/")

Using the subprocess wrapper - use with caution

(This is only recommended for testing or if for some reason you don't want / can't set up the REST service.)

To create a wrapper object using the subprocess variant, a psytricks.wrapper.PSyTricksWrapper with the address of the Delivery Controller to connect to has to be instantiated, for example:

from psytricks.wrapper import PSyTricksWrapper

wrapper = PSyTricksWrapper(deliverycontroller="cdc01.vdi.example.xy")

Fetching status information

The wrapper object can then be used to e.g. retrieve information on the machines controlled ("brokered") by Citrix:

machines = wrapper.get_machine_status()

for machine in machines:
    print(f"[{machine["DNSName"]}] is in power state '{machine["PowerState"]}'")
print(f"Got status details on {len(machines)} machines.")

Performing actions

To restart a machine, use something like this:

wrapper.perform_poweraction(machine="vm23.vdi.example.xy", action="restart")

For placing a machine in Maintenance Mode use:

wrapper.set_maintenance(machine="vm42.vdi.example.xy", disable=False)

PSyTricks Change Log

2.1.6

Fixed

2.1.5

Changed

  • Type hints and docstrings were fixed, some of them were indicating the wrong return types.

2.1.4

Changed

  • Logging level changes only, reducing messages higher than debug.

2.1.3

Changed

  • The version check done when instantiating a psytricks.wrapper.ResTricksWrapper object now allows for a PATCH level mismatch as by definition the API is still expected to be fully compatible. It issues a warning-level log message to indicate the mismatch though. This allows for easy fixes on the Python side of the package without having to re-install / upgrade the server side each time. A small fix to respect pre-releases following the semantic versioning rules has been added as well.

2.1.2

Changed

  • Updated [loguru] dependency to 0.7.0.

2.1.1

No functional / code changes, only lowering minimal Python version to 3.9.

2.1.0

PowerShell changes

The JSON returned by the REST server was completely missing the Status object, this is now fixed. Additionally, that object now also contains a Timestamp property to report (in seconds since the epoch, a.k.a. Unix time) when the response has been generated.

Added

  • The constructor of psytricks.wrapper.ResTricksWrapper is now doing a connection check to the defined server upon instantiation.
  • In addition it was extended by an optional argument verify (defaulting to True) for requesting the server version to be verified against the client version.
  • If verify is set to True exceptions will be raised in case the connection check fails or if a version mismatch is detected.

Changed

  • In case the core HTTP request (GET or POST) fails in any of the wrapper methods, the corresponding exception is now re-raised to make this visible to the calling code. Previously only log messages were generated and the exceptions had been silenced explicitly.
  • Each HTTP response is now expected to contain a JSON payload. In case the HTTP status code is indicating an issue, the Status attributes of the returned JSON are printed to the log to facilitate debugging.
  • The psytricks.wrapper.ResTricksWrapper.perform_poweraction method now returns the details on the power action status of the given machine.

Fixed

2.0.0

Common

A REST server (restricks-server.ps1) written in PowerShell was added to facilitate and speed up interaction with the CVAD / Citrix toolstack. See the installation instructions for details on how to use it and please be aware that this is very much in an infant state ๐Ÿผ.

NOTE: this is an alternative to the original way of calling PowerShell as a Python subprocess (requiring the Python code to be executed in a user context that has access to the Citrix Broker Snap-In and that has appropriate permissions configured on the Delivery Controller). Wrapping this into a REST service that is reachable via HTTP allows to run the Python code in a completely independent context.

Changed

  • ๐Ÿงจ BREAKING ๐Ÿงจ
    • psytricks.wrapper.PSyTricksWrapper.send_message is now requiring the details (style, title and text) of the message to send directly, unlike before where a file was being read. If the file use-case is required again it can be simply wrapped around the method call. This change is done to keep consistency with the newly introduced REST wrapper class (see below).
    • All action methods (requesting data or state changes from Citrix) in psytricks.wrapper.PSyTricksWrapper have dropped the kwargs parameter. They were initially implemented to simplify the call from within psytricks.cli.run_cli but are basically just adding confusion.
    • To accommodate for the above changes the CLI command sendmessage now has two additional command-line options: --title (mandatory) to set the message title and --style (optional) to set the message icon. The previously existing --message option now expects the body as a string (may contain \n for linebreaks), not the path to a message file any more.

Added

  • An additional wrapper class psytricks.wrapper.ResTricksWrapper ๐ŸŽช has been added to interface with the newly introduced REST service. Apart from feeling much less awkward than the subprocess way, this also happens to be orders of magnitude faster ๐ŸŽข๐ŸŽก.
  • Added psytricks.literals to improve type checking and documentation.

1.1.0

Added

  • Mapping for the Action item when requesting a poweraction (see psytricks.mappings for details).

1.0.0

Changed

  • ๐Ÿงจ BREAKING ๐Ÿงจ
    • The PowerShell wrapper script parameter JsonConfig has been dropped in favor of the new AdminAddress that gives the address of the Delivery Controller to use directly. This is done for two reasons: to be more consistent with the original Citrix commands and to get rid of the overhead of needing an extra file when it contains only one single setting.
    • To accommodate for this, the PSyTricksWrapper constructor changed its only parameter from conffile to deliverycontroller.
    • Along those lines also the CLI tool switched from using the parameter --config to --cdc (short for Citrix Delivery Controller).
  • When requesting session details (PSyTricksWrapper.get_sessions) the following properties will now also be reported:
    • ClientAddress
    • ClientName
    • ClientPlatform
    • ClientProductId
    • ClientVersion
    • ConnectedViaHostName

Added

  • Two new arguments for the command line tool:
    • --version
    • --outfile - to write the results into a file

0.1.0

Added

  • Implementation of CLI commands and related wrapper methods (in braces):
    • getaccess (get_access_users())
    • setaccess (set_access_users())
    • maintenance (set_maintenance())
    • sendmessage (send_message())
    • poweraction (perform_poweraction())

Changed

  • Various *State fields reported by Citrix are now being mapped from numerical values to their descriptive (human readable) names, just as PowerShell does it when converting the resulting objects to text (e.g. for console output).
  • In case of an error while parsing the JSON returned by the PS1 script the raw data is now included in the error message.
  • Machine properties now contain the DNSName field.
  • Session properties now contain the Uid field.

0.0.3

Added

  • Preliminary implementation of the disconnect command.
  • Infrastructure for the PowerShell wrapper script for simulating calls to the Citrix stack. This is particularly useful for testing (e.g. on Linux) when no actual Citrix infrastructure is available.
  • Some timing / profiling information is now shown in the debug log.
  • Sample JSON data for the sessions command.

Changed

  • The command CLI argument is now an option, requiring --command to be used.
  • The JSON returned by the PowerShell wrapepr script now contains two objects (Status and Data), making it possible to return proper exit codes and error messages to the calling code.
  • Date strings in the JSON formatted by PowerShell 5.1 are now properly parsed into Python datetime objects.
  • Sample JSON data is now shipped with the module, facilitating debugging and testing.
1"""
2.. include:: ../../README.md
3
4.. include:: ../../CHANGELOG.md
5"""
6
7__version__ = "0.0.0"