let squashStrArray (str_arg : string[]) = let array_len = str_arg.Length let rec inner_squash (inner_str_arg : string []) (build_up_list : string) idx = if idx = array_len then build_up_list else let build_up_list = inner_str_arg.[idx] + " " + build_up_list inner_squash inner_str_arg build_up_list (idx + 1) inner_squash str_arg "" 0
Tag: F#
I discovered a nice utility that accompanies Visual Studio; at least it is in Visual Studio 2015. It is called ilmerge. It specific purposes is to fold an executable along with any dependent DLLs into a new executable that requires no external dependencies. You might need this for those time when you want a single executable on, say, a remote system.
Here is a command that binds two .Net libraries, one a distributed Microsoft API and the other is part of my program.
ilmerge /log:ilmerge.log ` /lib:C:\Windows\Microsoft.NET\Framework\v4.0.30319 ` /target:winexe /targetplatform:v4 ` /out:FreeBytesC.exe ` /ndebug FreeBytes.exe ` FreeBytes.exe FSharp.Core.dll VolLib.dll
The library and application have been updated to be a little more flexible and take a parameter, so that the minimum free space does not have to be hard-coded into the program.
Here is VolLib, an F# library:
namespace Toa.volLib open System open System.Threading open System.Collections.Generic open System.Text open System.IO open Microsoft.Win32 type DiskFreeLevels = | DRIVE_OFF_LINE = -1L | GB1 = 1000000000L | GB5 = 5000000000L | GB10 = 10000000000L | GB20 = 20000000000L [<AutoOpen>] module volLib = let libInit = ref false let drive_list () = DriveInfo.GetDrives() // Takes a string representation of a drive, and pulls out its free space. let free_space drive = let di = DriveInfo(drive) if di.IsReady then int64(di.AvailableFreeSpace) else int64(-1) // Just convert bytes to gigabytes. let cvt_bytes_to_gb (in_bytes:int64) = int64(truncate((float(int64(in_bytes)) / float(1024L) / float(1024L)) / float(1024L))) let DRIVE_OFF_LINE = int64(-1) let GB1 = cvt_bytes_to_gb(int64(DiskFreeLevels.GB1)) let GB5 = cvt_bytes_to_gb(int64(DiskFreeLevels.GB5)) let GB10 = cvt_bytes_to_gb(int64(DiskFreeLevels.GB10)) let GB20 = cvt_bytes_to_gb(int64(DiskFreeLevels.GB20)) // This is a pipeline function that takes a drive letter (string) and // returns the free GB from a DriveInfo structure. let cvt_drive_to_free_gb drive = let fs = free_space drive if fs > DRIVE_OFF_LINE then free_space drive |> cvt_bytes_to_gb else int64(-1) let cvt_gb_arg_to_int (arg:string) = let numeric_arg = arg.Substring(2, arg.Length - 4) int64(numeric_arg)
and the main application
(* A simple checker of Windows disks. Charles M. Norton 11/01/2016 Will be greatly improved over time. Modifications Charles M. Norton 11/1/2016 Initial version. *) open System open System.Text open System.Net.Mail open System.IO open System.Threading open Microsoft.VisualBasic.FileIO open System.Collections.Generic open Toa.volLib // local library let send_email msg = use msg = new MailMessage ("dbadmin@somewhere.sometown.us", @"dbadmin@town.arlington.ma.us", @"Disk Space Report\n", msg ) use client = new SmtpClient(@"webmail.somewhere.sometown.us") client.DeliveryMethod <- SmtpDeliveryMethod.Network client.Credentials <- new System.Net.NetworkCredential("dbadmin", "xxxxxx") client.Send msg let match_head = "--" let match_tail = "GB" let contains_match_constants (arg:string) = if arg.StartsWith match_head && arg.EndsWith match_tail then true else false let parse_min_gb_arg arg = if contains_match_constants arg then cvt_gb_arg_to_int arg else volLib.GB1 let no_disk = int64(-1) [<EntryPoint>] let main argv = let min_gb = if argv.Length > 0 then parse_min_gb_arg argv.[0] else volLib.GB1 let local_drive_list = drive_list () let check_file = "free_bytes_check.txt" let system_name = System.Environment.MachineName let system_drive = Path.GetPathRoot(Environment.SystemDirectory) let display_info = new List<string>() display_info.Add("System name " + system_name + "\n") let drive_list_len = local_drive_list.Length for idx = 0 to (drive_list_len - 1) do let drive_name = (Seq.item idx local_drive_list).Name let free_gb = cvt_drive_to_free_gb drive_name let display_row = "Volume: " + drive_name + "\n" + "Free Space: " + free_gb.ToString() + "GB\n" display_info.Add(display_row) let curr_time = DateTime.Now let gb_c = cvt_drive_to_free_gb system_drive let gb_d = cvt_drive_to_free_gb "D:\\" let gbs = display_info |> String.concat("\n") if not (File.Exists check_file) then use swH = new StreamWriter(check_file, true) swH.WriteLine(curr_time.ToString()) send_email gbs else let fAccessDate = File.GetLastAccessTime(check_file) let update_stamp = curr_time.Subtract(fAccessDate).Duration() < TimeSpan.FromDays(7.0) if not(update_stamp) then File.WriteAllText(check_file.ToString(), String.Empty) use swH = new StreamWriter(check_file, true) swH.WriteLine(curr_time.ToString()) send_email gbs if gb_c < min_gb || ((gb_d > no_disk) && gb_d < volLib.GB20) then printfn "Disk free space is below thresholds; send out warning email." send_email gbs else printfn "Disk free space is at or above thresholds. " printfn "All is well, at least for now."</pre> <pre> printfn "Sleeping 5 seconds to let you read." Thread.Sleep 5000 |> ignore 0 // return an integer exit code
So, here is a small, F# program to report on disk usage. This is from a real example on our Munis server.
(* A simple checker of Windows disks. Charles M. Norton 11/01/2016 Will be greatly improved over time. Modifications Charles M. Norton 11/1/2016 Initial version. *) open System open System.Text open System.Net.Mail open System.IO open System.Threading open Toa.volLib // local library let SendEmail msg = use msg = new MailMessage ( "dbadmin@town.arlington.ma.us", @"itadmin@town.arlington.ma.us", @"Munis Server Disk Space Report\n", msg ) use client = new SmtpClient(@"webmail.town.arlington.ma.us") client.DeliveryMethod client.Credentials client.Send msg let main argv = let localDriveList = driveList () let dn = (Seq.head(localDriveList)).Name let drive_c = (Seq.head(localDriveList)).Name let data_drive = (Seq.nth(1) localDriveList).Name let fs_c = freeSpace drive_c let fs_d = freeSpace data_drive let gb_c = cvtBytesToGB fs_c let gb_d = cvtBytesToGB fs_d let system_name = System.Environment.MachineName let strings = ["System name: "; system_name; "Volume "; drive_c; gb_c.ToString(); "GB\n"; "Volume"; data_drive; gb_d.ToString(); "GB\n"] let gbs = String.concat " " strings printfn "System name: %A: Drive C %f -- Data Drive %f" system_name gb_c gb_d if gb_c < volLib.GB5 || gb_d < volLib.GB5 then printfn "Disk free space is below thresholds; send out warning email." SendEmail gbs else printfn "Disk free space is at or above thresholds. All is well, at least for now." printfn "Sleeping 5 seconds to let you read." Thread.Sleep 5000 |> ignore 0 // return an integer exit code
I got a very nice answer on SO to why a function was not executing.
open System open System.Collections.Generic open System.Text open System.IO #nowarn "40" let rec readlines () = seq { let line = Console.ReadLine() if not (line.Equals("")) then yield line yield! readlines () } [<EntryPoint>] let main argv = let inSeq = readlines () inSeq |> Seq.length |> printfn "%d lines read" // This will keep it alive enough to read your output Console.ReadKey() |> ignore 0