Starting with Icinga [PowerShell Framework v1.1.0](https://icinga.com/docs/windows/latest) plenty of features and functionality have been added for shipping data by using a REST-API. This Developer Guide will describe on how to write custom API endpoints by using the [PowerShell Framework v1.1.0](https://icinga.com/docs/windows/latest) and the [Icinga PowerShell REST-Api](https://icinga.com/docs/windows/latest/restapi/doc/01-Introduction/). In this example we will write a custom endpoint to simply provide a file list for a specific folder.
Like plugins, API endpoints can contain plenty of different files to keep the code clean. To ensure each module is identical and easier to maintain for users, we would advise the following file structure:
```text
module
|_ apiendpoint.psd1
|_ apiendpoint.psm1
|_ lib
|_ function1.psm1
|_ function2.psm1
```
This will ensure these functions can be called separately from the endpoint itself and make re-using them a lot easier. In addition, it will help other developers to build dependencies based on your module and allow an easier re-usage of already existing components.
Additional required files within the `lib` folder can be included by using the `NestedModules` array within your `psd1` file. This will ensure these files are automatically loaded once a new PowerShell session is started.
## Creating A New Module
The best approach for creating a custom API endpoint 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 other module updates.
In this guide, we will assume the name of the module is `icinga-powershell-apitutorial`.
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
```powershell
C:\Program Files\WindowsPowerShell\Modules
```
Now create a new folder with the name `icinga-powershell-apitutorial` and navigate into it.
As we require a `psm1` file which contains our code, we will create a new file with the name `icinga-powershell-apitutorial.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-MyIcingaAPITutorialCommand`.
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-MyIcingaAPITutorialCommand` 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 this is working, we can remove the function again as we no longer require it.
## Create A New API-Endpoint
Once everything is working properly we can create our starting function we later use to execute our API endpoint.
At first we create a new folder `lib` inside our module folder and inside the file `Invoke-IcingaAPITutorialRESTCall.psm1`. For naming guidelines we will have to use `Invoke-Icinga{0}RESTCall`. Replace `{0}` with a unique name describing shortly what your module is doing. The user will not require to use this function later and is only required internally and to have a better look on which function is providing REST endpoints.
A developer using the REST-Api integration does not have to worry about anything regarding header fetching, URL encoding or similar. All data is parsed by the [Icinga PowerShell REST-Api](https://icinga.com/docs/windows/latest/restapi/doc/01-Introduction/) and invoked to our function.
For our API endpoint we will start with `param()` to parse arguments to our endpoint which is `standardized`, and has to be followed. Otherwise the integration might not work.
```powershell
function Invoke-IcingaAPITutorialRESTCall()
{
# Create our arguments the REST-Api daemon will use to parse the request
The request path is split into two hashtable entries: `FullPath` and `PathArray`. This tells you exactly which URL the user specified and allows you to build proper handling for different entry points of your endpoint.
For the path array, on index 0 you will always find the `version` and on index 1 `your endpoint alias`. Following this, possible additional path extensions in your module will always start on index 2.
##### Header
A hashtable containing all send headers by the client. If you require your client to send additional headers for certain tasks to work, you can check with this if the header is set with the correct value.
```powershell
Name Value
---- -----
Upgrade-Insecure-Requests 1
User-Agent Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
Accept text/html,application/json
Host example.com:5668
Sec-Fetch-Dest document
Accept-Language de,en-US;q=0.9,en;q=0.8
Connection keep-alive
Accept-Encoding gzip, deflate, br
Sec-Fetch-Mode navigate
sec-ch-ua-mobile ?0
X-CustomHeader Custom Content
```
##### RequestArguments
Of course we will also handle possible request arguments. This could either be used for filtering or to modify returned content depending on the input. An example could look like this:
The content send by the client in case a method is used to send data.
**Note**: The body argument is only available in case data is send. If the client is using `POST` and sending no data, the argument is not present.
##### FullRequest
This argument contains the full request string for possible troubleshooting and debugging.
```text
/v1/apitutorial?include=*psm1&exclude=*api*
```
##### ContentLength
This only applies to any request which can send data as body and tells you how many data was send. This part is moved from the header to this location for easier accessing.
#### Connection Argument
This argument is containing the connection details of the client including the TCP stream object. You only require this for sending data back to the client or for troubleshooting. In general you only have to parse this object to other functions without modifying it.
Now we are basically ready to process data. To do so, we will fetch the current folder content of our PowerShell module with `Get-ChildItem` and send this content to our client. For sending data to the client, we can use `Send-IcingaTCPClientMessage`. This Cmdlet will use a `Message` as `New-IcingaTCPClientRESTMessage` object which itself contains the `HTTPResponse` and our `ContentBody`. In addition to `Send-IcingaTCPClientMessage` we also have to specify the `Stream` to write to. The stream object is part of our `Connection` argument.
Now as we have written a basic function to fetch folder content and to send it back to our client, we will have to `register` our Cmdlet to the endpoint. For this we will open our `icinga-powershell-apitutorial.psm1` and add a `namespace` function which has to follow this naming guideline: `Register-IcingaRESTAPIEndpoint{0}`
Replace `{0}` with the name you have chosen for your `Invoke-Icinga{0}RESTCall`. Once the REST-Api Daemon is loaded, all functions within this namespace are executed. The function has to return a hashtable with an `Alias` referring to the URL part the user has to enter and a `Command` being executed for this alias.
function Register-IcingaRESTAPIEndpointAPITutorial()
{
return @{
'Alias' = 'apitutorial';
'Command' = 'Invoke-IcingaAPITutorialRESTCall';
};
}
```
If our module is providing different endpoints, you will have to create multiple register functions. To keep the API how ever clean and prevent conflicting, we advice you to provide only `one` endpoint and handle all other tasks within this endpoint.
As everything is now ready, we can restart our Icinga PowerShell Framework service by using
This is a basic tutorial on how to write custom API-Endpoints and make them available in your environment. Of course you can now start to filter requests depending on the URL the user added, used headers or other input like the body for example. All data send by the client is accessible to developers for writing their own extensions and modules.