By installing the PowerShell Framework as service you have the possibility to [register custom daemons](../110-Installation/05-Background-Daemons.md) which are executed in the background. This developer guide article will assist you in creating custom daemons.
The best approach for creating a custom daemon is by creating an independent module which is installed in your PowerShell modules directly. This will ensure you are not overwriting your custom data with possible framework updates.
At first we will have to create a new module. Navigate to the PowerShell modules folder the Framework itself is installed to. In this tutorial we will assume the location is
As we require a `psm1` file which contains our code, we will create a new file with the name `icinga-powershell-agentservice.psm1`. This will allow the PowerShell autoloader to load the module automatically.
**Note:** It could be possible, depending on your [execution policies](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.security/set-executionpolicy?view=powershell-6), that your module is not loaded properly. If this is the case, you can try to unblock the file by opening a PowerShell and use the `Unblock-File` Cmdlet
Now open a **new** PowerShell terminal or write `powershell` into an already open PowerShell prompt and execute the command `Test-MyIcingaAgentServiceCommand`.
If everything went properly, you should now read the output `Module was loaded` in our prompt. If not, you can try to import the module by using
inside your console prompt. After that try again to execute the command `Test-MyIcingaAgentServiceCommand` and check if it works this time. If not, you might check the naming of your module to ensure `folder name` and `.psm1 file name` is identical.
Once everything is working properly we can create our starting function we will later use for [registering our daemon](../110-Installation/05-Background-Daemons.md).
For naming guidelines we will have to begin with the `Start` naming and an identifier of what are going to achieve with our daemon. In our example we will frequently check if the Icinga 2 agent service is active and running. In case it failed, we will restart the service.
A basic daemon consists of two parts. At first we require a function our daemon loader will start, creating a new thread by using `New-IcingaThreadInstance`. In addition, we require another function which will then be executed as our thread call.
Depending on our daemon, later usage and possible sharing of data between all loaded daemons might be required. In addition we might want to spawn child threads as single tasks being executed. To access the global, shared data, use can use the `Global:Icinga.Public` variable. This content is shared automatically between each single created thread.
Now as the basic part is finished, we will require to make our framework libraries available within this thread. To do so, we will initialise the framework with the `Use-Icinga` Cmdlet, do however only import libraries and tell the framework that the wish to utilize it as `daemon`. The last part is important, as this will change the handling for writing console outputs and instead of an `exit` for certain failures the module will log them internally.
As we will parse the `global` framework data anyways, we should already make use of it. In this case, we will write the current service state of Icinga 2 into a global `synchronized` hashtable. Before we can do this, we will have to add a new hashtable to our background daemons
As now our base skeleton for daemon is ready we can start to write the actual part which will execute the code to check for our Icinga Agent service state.
Because the code is executed as separate thread, we will have to ensure it will run as long as the PowerShell service is being executed. This will be done with a simple `while` loop
*ALWAYS* ensure you add some sort for `sleep` at the end of the `while` loop to allow your CPU some breaks. If you do not do this, you might suffer from high CPU loads. The `sleep duration` interval can depend either on a simple CPU cycle break or by telling the daemon to execute tasks only in certain Intervalls. In our case we wish to execute the daemon every `5 seconds`.
This is basically the foundation of every single daemon you will write. Now we will add the actual task our daemon will execute while it is running. As mentioned before, we will test if our Icinga 2 Agent service is running and restart it if it is stopped. To keep track of the current status and possible errors during restart, we will add additional `synchronized` hashtables to store the `value` of the current service status and possible `restart_error` counts. To count the `restart_error` we will have to initialises a single variable we name `$RestartErrors` before we enter our `while` loop.
As arguments we will have to add a unique `name` to use for this thread as well as a `thread pool`, on which the function will be added to. The name of the thread is automatically constructed based on the caller function, the function being executed and the given name. If you create a thread which is used as a `main` thread for several sub-threads, you could call it `Main`. For our example, the constructed thread name will then internally be `Start-IcingaAgentServiceTest::IcingaAgentServiceTest::Main::0`. The last added digit will auto increment, in case you are adding the identical thread multiple times, allowing you to access certain threads again by their index id. For the thread pool, we will use the default thread pool `MainPool` of Icinga for Windows. To customize this, you can use `Add-IcingaThreadPool` with a unique name and the amount if allowed instances. Last but not least we require to parse possible `Arguments` to our function and tell the thread to `Start` right after being created.
**Note:** In order to restart the Icinga Agent service, the user the PowerShell service is running with requires these kind of privileges. Otherwise it will throw an error and the error counter will increase
During development you might want to test the current implementation and check if everything is working as intended. To do so, you require to open a PowerShell terminal as `administrator`. We would recommend to `stop` the PowerShell service in this case to prevent possible daemons writing files to the system and overwriting each others.
Once the service is stopped and your `administrative PowerShell` is open, we will have to initialise the Framework and start the background daemon component
Once done you will receive back your prompt, however all registered background daemons are running. To access the collected data from daemons, you can print the content of the `global` framework data. If you wish to check if your daemon was loaded properly and data is actually written, we can access our created hashtable and get the current service state of it