Error Installing a Windows Service (in F#)

372 Views Asked by At

my question is the following: When I try to install my Windows Service I get the following error:

snippet: ... No public installers with the RunInstallerAttribute.Yes attribute could be found in the <path to exe> assembly. ...

I follow this tutorial

I have one Program.fs file containing:

[<RunInstaller(true)>]
type public FSharpServiceInstaller() =
    inherit Installer()
    do
        < some logic, doesn't really matter >

This should be sufficient, as a matter of fact, I don't even think I need to add the public keyword to the type definition. Installing this executable with InstallUtil.exe gives me the same error as installing it using the following code:

[<EntryPoint>]
let main args =

    if Environment.UserInteractive then
        let parameter = String.Concat(args);
        match parameter with
        | "-i" -> ManagedInstallerClass.InstallHelper [| Assembly.GetExecutingAssembly().Location |]
        | "-u" -> ManagedInstallerClass.InstallHelper [| "/u"; Assembly.GetExecutingAssembly().Location |]
        | _ -> printf "Not allowed!\n" 
    else 
        ServiceBase.Run [| new CreditToolsService() :> ServiceBase |];
    0

I have tried running this script in PowerShell, cmd and Visual Studio CLI as both administrator and my normal account but I keep getting the same error. If anyone knows what I'm doing wrong I would really appreciate some help.

2

There are 2 best solutions below

1
Baudin999 On BEST ANSWER

OK, so here goes...

I've looked at the code provided by user1758475 and just randomly started copy pasting solutions into an application. Don Symes's solution "just worked" and I finally figured out why: I did not (and he does) have a namespace declaration, in my source. Seems like this was the culprit! After I added the namespace the installer worked like a charm.

As Curt Nichols pointed out, the installer should not be in a module because a module effectively hides the type from the calling code.

Thank you for help in figuring this out.

For those of you who want to see a working example:

namespace FileWatcher
open System
open System.Reflection
open System.ComponentModel
open System.Configuration.Install
open System.ServiceProcess
open System.IO
open System.Configuration

type FileWatcherService() =
    inherit ServiceBase(ServiceName = "FileWatcher")

    let createEvent = fun (args: FileSystemEventArgs) -> 
                    printf "%s has been %s\n" args.FullPath (args.ChangeType.ToString().ToLower()) 
                    |> ignore

    override x.OnStart(args) =
        let fsw = new FileSystemWatcher ()
        fsw.Path                    <- "C:\TEMP"
        fsw.NotifyFilter            <- NotifyFilters.LastAccess ||| NotifyFilters.LastWrite ||| NotifyFilters.FileName ||| NotifyFilters.DirectoryName ||| NotifyFilters.CreationTime
        fsw.Filter                  <- "*.txt"
        fsw.EnableRaisingEvents     <- true
        fsw.IncludeSubdirectories   <- true
        fsw.Created.Add(createEvent)

    override x.OnStop() =
        printf "Stopping the FileWatcher service"

[<RunInstaller(true)>]
type public FSharpServiceInstaller() =
    inherit Installer()
    do 

        // Specify properties of the hosting process
        new ServiceProcessInstaller
            (Account = ServiceAccount.LocalSystem)
        |> base.Installers.Add |> ignore

        // Specify properties of the service running inside the process
        new ServiceInstaller
            ( DisplayName = "AAA FileWatcher Service", 
            ServiceName = "AAAFileWatcherService",
            StartType = ServiceStartMode.Automatic )
        |> base.Installers.Add |> ignore


module Program =
    [<EntryPoint>]
    let main args =

        printf "starting the application...\n"


        if Environment.UserInteractive then
            let parameter = String.Concat(args);
            match parameter with
            | "-i" -> ManagedInstallerClass.InstallHelper [| Assembly.GetExecutingAssembly().Location |]
            | "-u" -> ManagedInstallerClass.InstallHelper [| "/u"; Assembly.GetExecutingAssembly().Location |]
            | _ -> printf "Not allowed!\n" 
        else 
            ServiceBase.Run [| new FileWatcherService() :> ServiceBase |];
        0
7
Helge Rene Urholm On

Working live production example at https://github.com/zbilbo/TB4TG/blob/master/TourneyBot.Service/Installer.fs

Think it needs to be installed with InstallUtil.exe though.

Possibly not the finest moment in coding, but that specific service code is from Don Syme more or less: http://blogs.msdn.com/b/dsyme/archive/2011/05/31/a-simple-windows-service-template-for-f.aspx, so it is probably fine, but the rest of the "surrounding" code on that repository may not be idiomatic ;-)

Don Symes blog also explains a lot more so it should be easily to adept it to your needs. It also links to a Win Service Template on VS Gallery: http://blogs.msdn.com/b/mcsuksoldev/archive/2011/05/31/f-windows-application-template-for-windows-service.aspx

Related Questions in F#