One of the many nice features of F# is its scripting capability. There are other scripting languages like PowerShell to improve your productivity on a Windows system. In this case you need to learn a new language and a new environment to automate some basic tasks. When you know F# and .NET you can leverage your skills, so let’s look at F# scripting.
A simple real-world problem
I once was responsible for integration testing a complex set of applications. After completing the test several applications were still running so these processes had to be stopped with the Task Manager and several directories had to be cleaned. How could F# help me to be more productive?
Let’s start by creating a directory called Scripts, add a new text file and rename it to EndIntegrationTest.fsx.
open the file with your favorite text editor, I use Notepad++ in this case and
add the line:
System.Diagnostics.Process.GetProcessesByName("app1") |> Seq.iter(fun x -> x.Kill())
This will “kill” all programs that are called app1 by first looking for all processes called app1 and next kill them one by one.
I have set the Language menu to Caml to get some syntax highlighting.
Next we change the program associated with the file extension, right click:
Look for another app on this PC (this is Windows 8) and look for the following directory:
C:\Program Files (x86)\Microsoft SDKs\F#\3.0\Framework\v4.0
and select fsi.exe.
To test the script I created a C# console app and called it app1, start the executable in the debug directory
Open the script file and app1 will be killed (R.I.P. app1).
This scripting solution has some drawbacks which we will discuss later, but it works. It matches the first rule of scripting:
Keep your scripts as simple as possible.
Scripts should increase your productivity and you should not spend much time on debugging or creating functionality you do not need (YAGNI –> You aren't gonna need it).
In case I want to kill app1 I have to go to the scripting directory. I can become more productive if I add a shortcut to my desk
rename it “Shortcut_EndIntegrationTest” and move it to the desktop.
The next step is to change some of the properties of the shortcut, Shortcut key and Run,
After these changes all I have to do is press CRTL + ALT + 1 after the test is completed and the script runs without showing a console.
One of the drawbacks of the the created solution is that we have associated the .fsx extension with the fsi.exe. In this way we can run an F# script by accident when we just want to edit a file. With great power, comes great responsibility. We can fix this when associate the .fsx files with a text editor program and repair the shortcut. Also from a security perspective this is a better solution.
Go to General tab and associate .fsx with an editor
After apply the shortcut and all .fsx file will start the text editor.
Next change he target line to:
"C:\Program Files (x86)\Microsoft SDKs\F#\3.0\Framework\v4.0\Fsi.exe" "E:\Blog\Scripts\EndIntegrationTest.fsx”
and the shortcut will kill app1 again (you need to repair the shortcut key and the run mode).
This script does not solve all our problems, we have to be able to kill multiple applications and we have to clean all polluted directories.
We could copy the line that killed the application and change the name of the app, but there is a second scripting rule:
Do not repeat yourself (DRY).
So we create a facade function killProcesses:
open System.Diagnostics
let killProcesses processNames =
Process.GetProcesses()
|> Seq.filter(fun p ->
processNames|> List.exists (fun name -> name = p.ProcessName))
|> Seq.iter(fun x -> x.Kill())
and call it with a list of application name:
killProcesses ["app1"; "app2"]
this replaces the initial code.
CTRL + ALT + 1
and all are processes are killed.
Next we add the facade function to clean the directories:
let deleteContentOfDirectory path =
Directory.EnumerateDirectories(path) |> Seq.iter(fun dir -> Directory.Delete(dir, true))
Directory.EnumerateFiles(path) |> Seq.iter(File.Delete)
We enumerate through all content in the directory, sub directories and files and delete them all. We use the facade function to clean all required directories:
["E:\\CleanUpTest1"; "E:\\CleanUpTest2"] |> List.iter(deleteContentOfDirectory)
Before:
After:
So we have solved our real-world problem.
Next time we improve our solution by putting the facade functions in a library and we look at startup scripts.
Update:At a windows machine not containing F# Script (REPL), you can add it yourself. Here are all the details: A step by step guide to installing an F# REPL environment on Windows by Matthew Adams.