Search code examples
f#windows-servicesservice-installer

Error Installing a Windows Service (in F#)


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.


Solution

  • 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