diff --git a/CSharp/ExecuteCommand.csharp b/CSharp/ExecuteCommand.csharp
new file mode 100644
index 0000000..ec5385f
--- /dev/null
+++ b/CSharp/ExecuteCommand.csharp
@@ -0,0 +1,59 @@
+using System;
+using System.IO;
+using System.Text;
+using System.Diagnostics;
+using System.Collections.Generic;
+
+namespace AniNIX.Shared {
+
+ public static class ExecuteCommand {
+
+ ///
+ /// This method allows a CSharp app to execute a command on the OS.
+ ///
+ /// The command string to run as the string argument to "bash -c 'command'"
+ /// The effective replacement for the command's stdinThe stdout of the command
+ ///
+ public static String Run(String command, String input) {
+ //Sanitize inputs.
+ if (command.Contains("\'")) {
+ throw new Exception("Command strings cannot include \'.");
+ }
+
+ //Create process.
+ Process proc = new Process();
+ proc.StartInfo.CreateNoWindow = true;
+ proc.StartInfo.FileName = "/bin/bash";
+ proc.StartInfo.Arguments = String.Format("-c \'{0}\'",command);
+ proc.StartInfo.UseShellExecute=false;
+ ReportMessage.Log(Verbosity.Verbose,String.Format("{0} {1}",proc.StartInfo.FileName,proc.StartInfo.Arguments));
+
+ //Redirect input
+ proc.StartInfo.RedirectStandardOutput=true;
+ proc.StartInfo.RedirectStandardInput=true;
+
+ //Start process
+ proc.Start();
+
+ //Add input and read output.
+ proc.StandardInput.Write(input);
+ proc.StandardInput.Close();
+ proc.WaitForExit();
+ if (proc.ExitCode != 0) {
+ throw new Exception(String.Format("Failed to exit command with return code {0}",proc.ExitCode));
+ }
+ String stdoutString = proc.StandardOutput.ReadToEnd();
+
+ //Close up and return
+ proc.Close();
+ return stdoutString;
+ }
+
+ //Add polymorphism to allow no stdin
+ public static String Run(String command) {
+ return Run(command,null);
+ }
+
+ }
+}
diff --git a/CSharp/ReportMessage.csharp b/CSharp/ReportMessage.csharp
new file mode 100644
index 0000000..a951af9
--- /dev/null
+++ b/CSharp/ReportMessage.csharp
@@ -0,0 +1,47 @@
+using System;
+
+namespace AniNIX.Shared {
+
+ public enum Verbosity {
+ Always = -2,
+ Error,
+ Quiet = 0,
+ Verbose,
+ VeryVerbose,
+ Explicit,
+ }
+
+ public static class ReportMessage {
+
+ // Set this statically here, but allow others to override.
+ public static Verbosity verbosity = Verbosity.Quiet;
+
+ ///
+ /// Log a new message for the user.
+ ///
+ public static void Log(Verbosity level,String message) {
+
+ if (level == Verbosity.Error) {
+ Console.Error.WriteLine(message);
+ return;
+ }
+
+ if (ReportMessage.verbosity == Verbosity.Quiet) {
+ return;
+ }
+
+ if (level == Verbosity.Always
+ || (ReportMessage.verbosity == Verbosity.Verbose && level == Verbosity.Verbose)
+ || (ReportMessage.verbosity == Verbosity.VeryVerbose && (level == Verbosity.Verbose || level == Verbosity.VeryVerbose))
+ || (ReportMessage.verbosity == Verbosity.Explicit && (level == Verbosity.Verbose || level == Verbosity.VeryVerbose || level == Verbosity.Explicit))
+ ) {
+ Console.WriteLine(message);
+ }
+
+ }
+
+ public static void Log(String message) {
+ Log(Verbosity.VeryVerbose,message);
+ }
+ }
+}
diff --git a/CSharp/WebPageAPI.csharp b/CSharp/WebPageAPI.csharp
new file mode 100644
index 0000000..b2fd906
--- /dev/null
+++ b/CSharp/WebPageAPI.csharp
@@ -0,0 +1,39 @@
+using System;
+using System.IO;
+using System.Net;
+using System.Net.Security;
+using System.Security.Cryptography.X509Certificates;
+using System.Text.RegularExpressions;
+
+namespace AniNIX.Shared {
+
+ public static class WebPageAPI {
+
+ // Thanks to MSDN for this regex.
+ // https://msdn.microsoft.com/en-us/library/ms998267.aspx
+ public static Regex URLRegEx = new Regex(@"(ht|f)tp(s?)\:\/\/[0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*(:(0-9)*)*(\/?)([a-zA-Z0-9\-\.\?\,\'\/\\\+&%\$#_]*)?");
+
+ ///
+ /// Get a webpage source -- we use this instead of WebClient because Mono doesn't handle SSL well on Linux.
+ /// /usr/bin/curl -s SOMEURL
+ ///
+ /// the webpage whose title we should get
+ /// the webpage source
+ public static string GetPage(String pageURL) {
+ return ExecuteCommand.Run(String.Format("/usr/bin/curl -s {0}",pageURL));
+ }
+
+ ///
+ /// Get a webpage title
+ /// Should be equivalent to:
+ /// /bin/bash -c '/usr/bin/curl -s SOMEURL | perl -l -0777 -ne "print \$1 if /\s*(.*?)\s*<\/title/si"'
+ ///
+ /// the webpage whose title we should get
+ /// the webpage title
+ public static string GetPageTitle(String pageURL) {
+ string source = GetPage(pageURL);
+ return Regex.Match(source, @"\
]*\>\s*(?[\s\S]*?)\", RegexOptions.IgnoreCase).Groups["Title"].Value;
+ }
+
+ }
+}