Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Writing a service in F#

Tags:

f#

I am back again, this time with a question on writing service in F#. I cannot seem to install the service using installutil. It gives me the following error.

$ installutil atfwindowsservice.exe
Microsoft (R) .NET Framework Installation utility Version 4.0.30319.18408
Copyright (C) Microsoft Corporation.  All rights reserved.

Running a transacted installation.

Beginning the Install phase of the installation.
See the contents of the log file for the C:\Dev\ATF\output\bin\Debug\atfwindowsservice.exe assembly's progress.
The file is located at C:\Dev\ATF\output\bin\Debug\atfwindowsservice.InstallLog.
Installing assembly 'C:\Dev\ATF\output\bin\Debug\atfwindowsservice.exe'.
Affected parameters are:
   logtoconsole =
   logfile = C:\Dev\ATF\output\bin\Debug\atfwindowsservice.InstallLog
   assemblypath = C:\Dev\ATF\output\bin\Debug\atfwindowsservice.exe
No public installers with the RunInstallerAttribute.Yes attribute could be found in the C:\Dev\ATF\output\bin\Debug\atfwindowsservice.exe assembly.

The code is given below. Any help is appreciated and thanks in advance.

Ramesh

namespace service

open System.ServiceProcess
open System.Runtime.Remoting
open System.Runtime.Remoting.Channels

type atf() =
    inherit ServiceBase(ServiceName = "atf win service")

    override x.OnStart(args) = ()
    override x.OnStop() = ()

The registering the service code:

// Learn more about F# at http://fsharp.net
// See the 'F# Tutorial' project for more help.=
open System
open System.ComponentModel
open System.Configuration.Install
open System.ServiceProcess

[<RunInstaller(true)>]
type 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 = "F# ATF Service", ServiceName = "atf",StartType = ServiceStartMode.Automatic ) |> base.Installers.Add |> ignore

// Run the chat service when the process starts
module Main =
    ServiceBase.Run [| new service.atf() :> ServiceBase |]
like image 848
Ramesh Kadambi Avatar asked Jun 26 '15 20:06

Ramesh Kadambi


3 Answers

I had the same problem. I eventually added the following code which works nicely and has the added benefit of not requiring installutil.exe. The service is able to install/uninstall itself by passing in the correct command line param. Keep all your code and add the following:

module Program =   

  let getInstaller() =
    let installer = new AssemblyInstaller(typedefof<atf>.Assembly, null);
    installer.UseNewContext <- true
    installer

  let installService() =
    let installer = getInstaller()
    let dic = new System.Collections.Hashtable()
    installer.Install(dic)
    installer.Commit(dic)

  let uninstallService() =
    let installer = getInstaller()
    let dic = new System.Collections.Hashtable()
    installer.Uninstall(dic)

  [<EntryPoint>]
  let main (args:string[]) = 
    match (args |> Seq.length) with
    |1 -> match (args.[0]) with
          |"-install" -> installService()
          |"-uninstall" -> uninstallService()
          |_-> failwith "Unrecognized param %s" args.[0]
    |_ -> ServiceBase.Run [| new atf() :> ServiceBase |]
    0

To install you can execute the following from the command line:

atfwindowsservice.exe -install
like image 171
Kevin Avatar answered Nov 16 '22 18:11

Kevin


I figured out how to write a self installing service using other examples on the web, especially this post on stack was useful: http://pingfu.net/programming/2011/08/11/creating-a-self-installing-windows-service-with-csharp.html

open System
open System.ServiceProcess
open System.Windows
open System.Threading
open System.Windows.Forms
open System.ComponentModel
open System.Configuration.Install
open System.Reflection
open Microsoft.Win32

type ATFServiceInstaller() =
    inherit Installer()


 let spi_ = new ServiceProcessInstaller(Account = ServiceAccount.LocalSystem)
    let si_ = new ServiceInstaller( DisplayName = "ATF Service", Description="ATF service", ServiceName = "atf",StartType = ServiceStartMode.Automatic ) 
    let dic_ = new System.Collections.Hashtable()
    let SVC_SERVICE_KET = @"SYSTEM\CurrentControlSet\Services"

    member this.install () = 
        base.Installers.Add(spi_) |> ignore
        let ret = base.Installers.Add(si_)
        let apath = sprintf "/assemblypath=%s" (Assembly.GetExecutingAssembly().Location)
        let ctx = [|apath; "/logToConsole=false"; "/showCallStack"|]
        this.Context <- new InstallContext("atfserviceinstall.log", ctx)

        base.Install(dic_)
        base.Commit(dic_)

    member this.uninstall() = 

        base.Installers.Add(spi_) |> ignore


        let ret = base.Installers.Add(si_)
        let apath = sprintf "/assemblypath=%s" (Assembly.GetExecutingAssembly().Location)
        let ctx = [|apath; "/logToConsole=false"; "/showCallStack"|]
        this.Context <- new InstallContext("atfserviceinstall.log", ctx)
        base.Uninstall(null)

module Main =
try
    let args = Environment.GetCommandLineArgs()

    match (args |> Seq.length) with
    | 2 -> match (args.[1]) with
           | "-install" -> let installer = new ATFServiceInstaller()
                           installer.install()
                           installer.Dispose()
           | "-uninstall" -> let installer = new ATFServiceInstaller()
                             installer.uninstall()
                             installer.Dispose()
           | _ -> failwith "Unrecognized param %s" args.[0]
    | _ -> ServiceBase.Run [| new atfservice.ATFService() :> ServiceBase |]
with
    | _ as ex -> MessageBox.Show(ex.ToString()) |> ignore
like image 40
Ramesh Kadambi Avatar answered Nov 16 '22 19:11

Ramesh Kadambi


I came across this question while having the same issue. I still needed to use InstallUtil.exe in the deployment and find out that the problem with your original code was a missing namespace in the main file.

I have found this framework for hosting .NET services http://topshelf-project.com/ which makes the development much easier and basically lets you create a console application which you can debug and also has a built-in Windows/Mono service installer. The only downside for me was a missing support for installation with InstallUtil.exe again but there is a solution for that too. Instead of adding ServiceProcessInstaller and ServiceInstaller to Installers in the class inherited from Installer override Install and Uninstall methods and make them run your executable with install/unistall parameter.

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

    override __.Install(stateSaver : System.Collections.IDictionary) =
        let assemblyPath = __.Context.Parameters.["assemblypath"]
        stateSaver.Add(assemblyIdentifier, assemblyPath)

        // runProcess assemblyPath "install"

        base.Install(stateSaver)

    override __.Uninstall(savedState : System.Collections.IDictionary) =
        let assemblyPath = savedState.[assemblyIdentifier].ToString()

        // runProcess assemblyPath "uninstall"

        base.Uninstall(savedState)

Full code at: https://gist.github.com/jbezak/eda4cc5864059b717e71beaec47db2d9

like image 2
Jakub Bezák Avatar answered Nov 16 '22 20:11

Jakub Bezák