Saturday 24 April 2021

Writing Windows Services - Creating a Windows Service

Introduction

Windows Services are the Microsoft Windows' implementation of the computer science daemon.

Creating the service

In Visual Studio go to File/New/Project.

On the dialogue select Windows Service (.NET Framework).

Name the project and click Create.

Rename the Service1.cs file. VS will offer to rename the references as well, agree to it.

In the Design Tab, select Properties from the short cut menu.

Name the service. This is at the bottom of the properties and is easy to overlook (the error starts "service name contains invalid characters").

Add an Event Log object.

View Code to see the code.

Add a custom event log.

        public MyService()
        {
            InitializeComponent();
            eventLog1 = new System.Diagnostics.EventLog();
            if (!System.Diagnostics.EventLog.SourceExists("MySource"))
            {
                System.Diagnostics.EventLog.CreateEventSource(
                    "MySource", "MyNewLog");
            }
            eventLog1.Source = "MySource";
            eventLog1.Log = "MyNewLog";
        }

Add polling to the OnStart.

Add method called by timer.

        protected override void OnStart(string[] args)
        {
            eventLog1.WriteEntry("In OnStart.");
            ServiceStatus serviceStatus = new ServiceStatus();
            serviceStatus.dwCurrentState = ServiceState.SERVICE_START_PENDING;
            serviceStatus.dwWaitHint = 100000;
            SetServiceStatus(this.ServiceHandle, ref serviceStatus);            // Set up a timer that triggers every minute.
            Timer timer = new Timer();
            timer.Interval = 60000; // 60 seconds
            timer.Elapsed += new ElapsedEventHandler(this.OnTimer);
            timer.Start();
            // Update the service state to Running.
            serviceStatus.dwCurrentState = ServiceState.SERVICE_RUNNING;
            SetServiceStatus(this.ServiceHandle, ref serviceStatus);
        }

If the start (and/or) stop process is expected to take some time, it is possible to add additional service status information.

Creating the installer

Select the Service code file in Solution Explorer and choose View Designer.

In the Design View select the background and Add Installer.

This will add ProjectInstaller.cs to the project.

Click on the serviceInstaller1 and select Properties

Ensure the service name is correct, add a description and a display name.

Set the StartType to Automatic.

Right click on serviceProcessInstaller1 and select Properties. Set the Account property to LocalSystem.



Build the service

In the Solution Explorer select the service project and right click and select properties.

On the Application tab select the service program as the Startup object.

Build the project via Build/Build on the menu.

Installing the Service

Open the Developer Command Prompt for Visual Studio as Administrator from the Windows Start menu.

Change Directory to the location of the service executable.

Enter the command: installutil "My First Service.exe"

Checking your work

In the search box on the task bar type "services" and select the "Services" app.

Check for your new service.

Again in the search box on the task bar type "event" and select the Event Viewer.

Expand the Applications and Services Logs and look for your new log. Refresh the log to see each new log entry as the timer triggers.

Updating your Service

Now your service is n place and running, but you need to update it. Now you can uninstall the service (see later), rebuild it and re-install it.

Alternatively, you can just stop the service (using the Services app), rebuild it, and restart the service.

Cleaning Up

Open the Developer Command Prompt for Visual Studio as Administrator.

Change the Directory to where the executable is located.

Enter the command

installutil /u "My First Service.exe"

To uninstall the service.

References

https://en.wikipedia.org/wiki/Windows_service

https://en.wikipedia.org/wiki/Daemon_(computing)

https://docs.microsoft.com/en-us/dotnet/framework/windows-services/walkthrough-creating-a-windows-service-application-in-the-component-designer

https://docs.microsoft.com/en-us/visualstudio/deployment/deploying-applications-services-and-components#create-an-installer-package-windows-desktop

https://docs.microsoft.com/en-us/dotnet/api/system.serviceprocess.servicecontroller

https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.eventloginstaller


Saturday 3 April 2021

Delivering files to the Browser

Sometimes you want to embed files in a browser, such as images or PDF files.

With modern browsers it is easy to just add some HTML to display a file.

 <object data="documents/Hello World.pdf" type="application/pdf" width="100%" height="800px">

 <p>It appears you don't have a PDF plugin for this browser.</p>

</object>

The file to be displayed needs to be in a valid location on a web server, either the webserver delivering the page or an external one accessed with a full URL.

This is fine if you do not care that it potentially exposes your file structure.


If your webserver runs a scripting language it is possible to script the delivery of the file - the file does not have to be located in a similar location to the web server files, nor does it need to be accessible by HTTP (though it can be but hidden on a SharePoint or cloud location).


The following uses ASP.NET Core to build a RESTFul interface to deliver selected files on receipt of a request.

If you have not built a Web API project before, it is worth running through the tutorial here (https://docs.microsoft.com/en-us/aspnet/core/tutorials/first-web-api?view=aspnetcore-5.0&tabs=visual-studio).

Web API to deliver selected files

This Web API uses a RESTFul interface to request a file, the file request is included within the URL, and in this case is just an integer.

Requests to the Web API have the form:

http://<domanin name>/getpdf/<file id>

The test web page builds on the HTML above, the data attribute containing the RESTFul URL to select the files.

<object data="https://localhost:44310/getpdf/0" type="application/pdf" width="100%" height="800px">

 <p>It appears you don't have a PDF plugin for this browser.</p>

</object>


GetPDFController.cs

The following code is for a controller (additional  code is required, use the set up from the tutorial).

using Microsoft.AspNetCore.Mvc;

using Microsoft.Extensions.Logging;

using System;

/// <summary>

/// Web App controller

/// Controller for RESTful access to PDFs

/// The last part of the URL is an integer that selects a specific PDF

/// https://&lt;host&gt;/getpdf/&lt;pdfID&gt;

/// </summary>

namespace GetPDF.Controllers

{

    [ApiController]

    [Route("[controller]")]

    public class GetPDFController : ControllerBase

    {

        private readonly ILogger<GetPDFController> _logger;


        public GetPDFController(ILogger<GetPDFController> logger)

        {

            _logger = logger;

        }


        /// <summary>

        /// Returns a PDF selected by <paramref name="pdfID"/

        /// THis example uses a simple switch to select the PDF,

        /// in a real application this might be search parameters or 

        /// >multiple values that build into the file path

        /// 

        /// Additionally, in a real application there would be a 

        /// requirement for the authentication of the requester.

        /// Additionally the PDF could be watermarked before being

        /// sent.

        /// </summary>

        /// <param name="pdfID">PDF identifier</param>

        /// <returns>PDF or 404</returns>

        [HttpGet]

        [Route("{pdfID:int}")]

        public IActionResult Get(int pdfID)

        {

            string filename;

            // This is for demonstration purposes only.

            // This would require a more complicated solution

            switch (pdfID)

            {

                case 0:

                    filename = @"C:\inetpub\wwwroot\documents\file-0.pdf";

                    return new PhysicalFileResult(filename, "application/pdf");

                case 1:

                    filename = @"C:\inetpub\wwwroot\documents\file-1.pdf";

                    return new PhysicalFileResult(filename, "application/pdf");

               default:

                    return NotFound();

            }


        }

    }

}

References

https://en.wikipedia.org/wiki/ASP.NET_Core

https://docs.microsoft.com/en-us/aspnet/core/tutorials/first-web-api?view=aspnetcore-5.0&tabs=visual-studio

https://docs.microsoft.com/en-us/aspnet/core/web-api/action-return-types?view=aspnetcore-5.0

https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.physicalfileresult?view=aspnetcore-5.0


Pimoroni Enviro board

 The Pimoroni Enviro pHAT format board combines  a BME280 temperature, pressure and humidity sensor, LTR559 light and proximity sensor, a microphone and small colour LCD.

The Enviro board comes packed in a neatly printed anti-static bag (I was too excited to photograph it before opening it up)




Pimoroni have a Python library and a bunch of examples.


Assembly was easy, and a a pair of stand-offs secured the sandwich.

Here is an example program that displays the temperature, humidity, atmospheric pressure, lux level and the current time.

import ST7735
from PIL import Image, ImageDraw, ImageFont
from fonts.ttf import RobotoMedium as UserFont
from bme280 import BME280
import time
import datetime

import os

try:
    # Transitional fix for breaking change in LTR559
    from ltr559 import LTR559
    ltr559 = LTR559()
except ImportError:
    import ltr559

print("Starting")
bme280 = BME280()

display_angle = 270 # 90 for upside dow relative to screen print

# Set up the display
disp = ST7735.ST7735(
    port=0,
    cs=1,
    dc=9,
    backlight=12,
    rotation=display_angle,
    spi_speed_hz=10000000
)

# Initialize display.
disp.begin()

# Width and height to calculate text position.
WIDTH = disp.width
HEIGHT = disp.height

# Calculate text position
x = 5
y = 0

# Set up canvas and font
img = Image.new('RGB', (WIDTH, HEIGHT), color=(0, 0, 0))
draw = ImageDraw.Draw(img)
path = os.path.dirname(os.path.realpath(__file__))
font_size = 20
font = ImageFont.truetype(UserFont, font_size)
small_font = ImageFont.truetype(UserFont, 16)
large_font = ImageFont.truetype(UserFont, 38)

text_colour = (255, 255, 255)
back_colour = (0, 50, 170)

def display_data():
    temperature = bme280.get_temperature()
    pressure = bme280.get_pressure()
    humidity = bme280.get_humidity()
    lux = ltr559.get_lux()
    prox = ltr559.get_proximity()
    draw.rectangle((0, 0, WIDTH, HEIGHT), back_colour)
    draw.text((x, y), "{:04.1f}C".format(temperature), font=font, fill=text_colour)
    draw.text((x, y+25), "{:04.1f}%".format(humidity), font=font, fill=text_colour)
    draw.text((x, y+50), "{:4.0f}hPa".format(pressure), font=font, fill=text_colour)
    # alternating seconds flash the colon separator
    if int(time.time()) % 2 == 0:
        draw.text((x+60,y), datetime.datetime.now().strftime("%H:%M"),
                  font=large_font,fill=text_colour)
    else:
        draw.text((x+60,y), datetime.datetime.now().strftime("%H %M"),
                  font=large_font,fill=text_colour)
    draw.text((x+100,y+55),"{:05.02f}lx".format(lux), font=small_font,fill=text_colour)
    disp.display(img)

# Main loop
while True:
    #print(bme280.get_temperature())
    display_data()
    time.sleep(1)