diff --git a/Aegisub.sln b/Aegisub.sln
index f0ba4d23215319b4ad138e2be112feec358771de..29f175cd7adc424b4fb07a6be7d9ce8bc88b9548 100644
--- a/Aegisub.sln
+++ b/Aegisub.sln
@@ -1,8 +1,10 @@
 Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 2012
+# Visual Studio 2013
+VisualStudioVersion = 12.0.21005.1
+MinimumVisualStudioVersion = 10.0.40219.1
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Aegisub", "aegisub\build\Aegisub\Aegisub.vcxproj", "{9DDDB9E5-E4A1-423D-A224-F6D4E5AAC06A}"
 	ProjectSection(ProjectDependencies) = postProject
-		{15C79E75-F5F6-451D-B870-94ED02AF257E} = {15C79E75-F5F6-451D-B870-94ED02AF257E}
+		{914A5B35-66B2-4293-BB6C-D93DA9BC68C6} = {914A5B35-66B2-4293-BB6C-D93DA9BC68C6}
 	EndProjectSection
 EndProject
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "csrihelper", "aegisub\build\csrihelper\csrihelper.vcxproj", "{C832EAF3-860D-4373-A02C-933626B47A5E}"
@@ -34,11 +36,9 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "fontconfig", "aegisub\build
 		{78B079BD-9FC7-4B9E-B4A6-96DA0F00248B} = {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}
 	EndProjectSection
 EndProject
-Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "BuildTasks", "aegisub\build\BuildTasks\BuildTasks.fsproj", "{15C79E75-F5F6-451D-B870-94ED02AF257E}"
-EndProject
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "fribidi", "aegisub\build\fribidi\fribidi.vcxproj", "{FB8E8D19-A4D6-4181-943C-282075F49B41}"
 	ProjectSection(ProjectDependencies) = postProject
-		{15C79E75-F5F6-451D-B870-94ED02AF257E} = {15C79E75-F5F6-451D-B870-94ED02AF257E}
+		{914A5B35-66B2-4293-BB6C-D93DA9BC68C6} = {914A5B35-66B2-4293-BB6C-D93DA9BC68C6}
 	EndProjectSection
 EndProject
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ffms2", "aegisub\build\ffms2\ffms2.vcxproj", "{AA137613-96A1-4388-8905-71345B4F8F87}"
@@ -55,7 +55,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "aegisub\build\zlib\
 EndProject
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "! Update Dependencies", "aegisub\build\deps\deps.vcxproj", "{472212DF-99E8-4B73-9736-8500616D8A80}"
 	ProjectSection(ProjectDependencies) = postProject
-		{15C79E75-F5F6-451D-B870-94ED02AF257E} = {15C79E75-F5F6-451D-B870-94ED02AF257E}
+		{914A5B35-66B2-4293-BB6C-D93DA9BC68C6} = {914A5B35-66B2-4293-BB6C-D93DA9BC68C6}
 	EndProjectSection
 EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{CEAEFCB9-3759-4D03-8D51-7287D7B7E7DF}"
@@ -66,6 +66,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{CEAEFC
 	EndProjectSection
 EndProject
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "wxWidgets", "aegisub\build\wx\wxWidgets.vcxproj", "{0518D6C0-7BF6-4FD1-91FB-191BD10DB2AC}"
+	ProjectSection(ProjectDependencies) = postProject
+		{914A5B35-66B2-4293-BB6C-D93DA9BC68C6} = {914A5B35-66B2-4293-BB6C-D93DA9BC68C6}
+	EndProjectSection
 EndProject
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libass", "aegisub\build\libass\libass.vcxproj", "{8804F253-DA67-4CC4-926B-0CD2AEE5778D}"
 EndProject
@@ -74,11 +77,16 @@ EndProject
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "boost", "aegisub\build\boost\boost.vcxproj", "{A649D828-A399-4D81-ADEF-94CFDBA7847F}"
 EndProject
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "icu", "aegisub\build\icu\icu.vcxproj", "{F934AB7B-186B-4E96-B20C-A58C38C1B818}"
+	ProjectSection(ProjectDependencies) = postProject
+		{914A5B35-66B2-4293-BB6C-D93DA9BC68C6} = {914A5B35-66B2-4293-BB6C-D93DA9BC68C6}
+	EndProjectSection
 EndProject
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "googletest", "aegisub\build\googletest\googletest.vcxproj", "{FBE51B37-8B12-41E8-B5E0-F00A06B4BCD2}"
 EndProject
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tests", "aegisub\build\tests\tests.vcxproj", "{49766286-2B5D-4177-A860-BD7CE1846EEF}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BuildTasks", "aegisub\build\BuildTasks\BuildTasks.csproj", "{914A5B35-66B2-4293-BB6C-D93DA9BC68C6}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Win32 = Debug|Win32
@@ -242,18 +250,6 @@ Global
 		{AD56899E-961B-47B7-BD0F-14D0DA50D141}.Release|Win32.Build.0 = Release|Win32
 		{AD56899E-961B-47B7-BD0F-14D0DA50D141}.Release|x64.ActiveCfg = Release|x64
 		{AD56899E-961B-47B7-BD0F-14D0DA50D141}.Release|x64.Build.0 = Release|x64
-		{15C79E75-F5F6-451D-B870-94ED02AF257E}.Debug|Win32.ActiveCfg = Debug|Any CPU
-		{15C79E75-F5F6-451D-B870-94ED02AF257E}.Debug|Win32.Build.0 = Debug|Any CPU
-		{15C79E75-F5F6-451D-B870-94ED02AF257E}.Debug|x64.ActiveCfg = Debug|Any CPU
-		{15C79E75-F5F6-451D-B870-94ED02AF257E}.Debug|x64.Build.0 = Debug|Any CPU
-		{15C79E75-F5F6-451D-B870-94ED02AF257E}.Debug-MinDep|Win32.ActiveCfg = Debug|Any CPU
-		{15C79E75-F5F6-451D-B870-94ED02AF257E}.Debug-MinDep|x64.ActiveCfg = Debug|Any CPU
-		{15C79E75-F5F6-451D-B870-94ED02AF257E}.Debug-Tests|Win32.ActiveCfg = Debug|Any CPU
-		{15C79E75-F5F6-451D-B870-94ED02AF257E}.Debug-Tests|x64.ActiveCfg = Debug|Any CPU
-		{15C79E75-F5F6-451D-B870-94ED02AF257E}.Release|Win32.ActiveCfg = Release|Any CPU
-		{15C79E75-F5F6-451D-B870-94ED02AF257E}.Release|Win32.Build.0 = Release|Any CPU
-		{15C79E75-F5F6-451D-B870-94ED02AF257E}.Release|x64.ActiveCfg = Release|Any CPU
-		{15C79E75-F5F6-451D-B870-94ED02AF257E}.Release|x64.Build.0 = Release|Any CPU
 		{FB8E8D19-A4D6-4181-943C-282075F49B41}.Debug|Win32.ActiveCfg = Debug|Win32
 		{FB8E8D19-A4D6-4181-943C-282075F49B41}.Debug|Win32.Build.0 = Debug|Win32
 		{FB8E8D19-A4D6-4181-943C-282075F49B41}.Debug|x64.ActiveCfg = Debug|x64
@@ -391,6 +387,14 @@ Global
 		{49766286-2B5D-4177-A860-BD7CE1846EEF}.Debug-Tests|x64.ActiveCfg = Debug|x64
 		{49766286-2B5D-4177-A860-BD7CE1846EEF}.Release|Win32.ActiveCfg = Release|Win32
 		{49766286-2B5D-4177-A860-BD7CE1846EEF}.Release|x64.ActiveCfg = Release|x64
+		{914A5B35-66B2-4293-BB6C-D93DA9BC68C6}.Debug|Win32.ActiveCfg = Debug|Any CPU
+		{914A5B35-66B2-4293-BB6C-D93DA9BC68C6}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{914A5B35-66B2-4293-BB6C-D93DA9BC68C6}.Debug-MinDep|Win32.ActiveCfg = Debug|Any CPU
+		{914A5B35-66B2-4293-BB6C-D93DA9BC68C6}.Debug-MinDep|x64.ActiveCfg = Debug|Any CPU
+		{914A5B35-66B2-4293-BB6C-D93DA9BC68C6}.Debug-Tests|Win32.ActiveCfg = Debug|Any CPU
+		{914A5B35-66B2-4293-BB6C-D93DA9BC68C6}.Debug-Tests|x64.ActiveCfg = Debug|Any CPU
+		{914A5B35-66B2-4293-BB6C-D93DA9BC68C6}.Release|Win32.ActiveCfg = Release|Any CPU
+		{914A5B35-66B2-4293-BB6C-D93DA9BC68C6}.Release|x64.ActiveCfg = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
diff --git a/aegisub/build/BuildTasks/BuildTasks.fsproj b/aegisub/build/BuildTasks/BuildTasks.csproj
similarity index 50%
rename from aegisub/build/BuildTasks/BuildTasks.fsproj
rename to aegisub/build/BuildTasks/BuildTasks.csproj
index e0242e081f4c46e50c0d41783e4055cc9531ffb1..8ea795379116dd55c26ff340a5c38166357047a1 100644
--- a/aegisub/build/BuildTasks/BuildTasks.fsproj
+++ b/aegisub/build/BuildTasks/BuildTasks.csproj
@@ -1,71 +1,73 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
-  <PropertyGroup>
-    <Configuration Condition="'$(Configuration)'==''">Debug</Configuration>
-    <Platform>AnyCPU</Platform>
-    <SchemaVersion>2.0</SchemaVersion>
-    <ProjectGuid>15c79e75-f5f6-451d-b870-94ed02af257e</ProjectGuid>
-    <OutputType>Library</OutputType>
-    <RootNamespace>BuildTakss</RootNamespace>
-    <AssemblyName>BuildTasks</AssemblyName>
-    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
-    <Name>BuildTasks</Name>
-    <ProjectName>BuildTasks</ProjectName>
-    <RestorePackages>true</RestorePackages>
-    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\..\</SolutionDir>
-    <TargetFSharpCoreVersion>4.3.0.0</TargetFSharpCoreVersion>
-  </PropertyGroup>
-  <Import Project="$(MSBuildThisFileDirectory)..\paths.props" />
-  <PropertyGroup>
-    <OutputPath>$(AegisubBinaryDir)</OutputPath>
-    <IntermediateOutputPath>$(AegisubObjectDir)</IntermediateOutputPath>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
-    <DebugSymbols>true</DebugSymbols>
-    <DebugType>full</DebugType>
-    <Optimize>false</Optimize>
-    <Tailcalls>false</Tailcalls>
-    <DefineConstants>DEBUG;TRACE</DefineConstants>
-    <WarningLevel>3</WarningLevel>
-    <StartAction>Program</StartAction>
-    <StartProgram>C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe</StartProgram>
-    <StartWorkingDirectory>Z:\src\temp\aegisub\aegisub</StartWorkingDirectory>
-    <StartArguments>/p:BuildProjectReferences=false build\deps\deps.vcxproj</StartArguments>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
-    <DebugType>pdbonly</DebugType>
-    <Optimize>true</Optimize>
-    <Tailcalls>true</Tailcalls>
-    <DefineConstants>TRACE</DefineConstants>
-    <WarningLevel>3</WarningLevel>
-  </PropertyGroup>
-  <PropertyGroup>
-    <MinimumVisualStudioVersion Condition="'$(MinimumVisualStudioVersion)' == ''">12</MinimumVisualStudioVersion>
-  </PropertyGroup>
-  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets" />
-  <ItemGroup>
-    <Compile Include="BuildTasks.fs" />
-    <Compile Include="DependencyFetchers.fs" />
-    <None Include="packages.config" />
-  </ItemGroup>
-  <ItemGroup>
-    <Reference Include="FSharp.Core, Version=$(TargetFSharpCoreVersion), Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
-      <Private>True</Private>
-    </Reference>
-    <Reference Include="ICSharpCode.SharpZipLib">
-      <HintPath>..\..\..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll</HintPath>
-    </Reference>
-    <Reference Include="Microsoft.Build" />
-    <Reference Include="Microsoft.Build.Engine" />
-    <Reference Include="Microsoft.Build.Framework" />
-    <Reference Include="Microsoft.Build.Utilities.v4.0" />
-    <Reference Include="mscorlib" />
-    <Reference Include="System" />
-    <Reference Include="System.Core" />
-    <Reference Include="System.Net" />
-    <Reference Include="System.Numerics" />
-    <Reference Include="System.Xml" />
-  </ItemGroup>
-  <Import Project="$(SolutionDir)\.nuget\nuget.targets" />
-</Project>
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{914A5B35-66B2-4293-BB6C-D93DA9BC68C6}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>BuildTasks</RootNamespace>
+    <AssemblyName>BuildTasks</AssemblyName>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\..\</SolutionDir>
+  </PropertyGroup>
+  <Import Project="$(MSBuildThisFileDirectory)..\paths.props" />
+  <PropertyGroup>
+    <OutputPath>$(AegisubBinaryDir)</OutputPath>
+    <IntermediateOutputPath>$(AegisubObjectDir)</IntermediateOutputPath>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="ICSharpCode.SharpZipLib">
+      <HintPath>..\..\..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.Build" />
+    <Reference Include="Microsoft.Build.Engine" />
+    <Reference Include="Microsoft.Build.Framework" />
+    <Reference Include="Microsoft.Build.Utilities.v4.0" />
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="DownloadTgzFile.cs" />
+    <Compile Include="ExecShellScript.cs" />
+    <Compile Include="MsysPath.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="ShellWrapper.cs" />
+    <Compile Include="UpdateFile.cs" />
+    <Compile Include="Utils.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="packages.config" />
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
+  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+       Other similar extension points exist, see Microsoft.Common.targets.
+  <Target Name="BeforeBuild">
+  </Target>
+  <Target Name="AfterBuild">
+  </Target>
+  -->
+</Project>
diff --git a/aegisub/build/BuildTasks/BuildTasks.fs b/aegisub/build/BuildTasks/BuildTasks.fs
deleted file mode 100644
index a7ff6658daffaf9883aac01b652358b1a1d2d267..0000000000000000000000000000000000000000
--- a/aegisub/build/BuildTasks/BuildTasks.fs
+++ /dev/null
@@ -1,127 +0,0 @@
-// Copyright (c) 2012, Thomas Goyne <plorkyeran@aegisub.org>
-//
-// Permission to use, copy, modify, and distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-//
-// Aegisub Project http://www.aegisub.org/
-
-module BuildTasks
-
-open System
-open System.Diagnostics
-open Microsoft.Build.Evaluation
-open Microsoft.Build.Framework
-open Microsoft.Build.Utilities
-
-/// Read all of the defined properties from the calling project file and stuff
-/// them in a Map
-let propertyMap (be : IBuildEngine) =
-  use reader = Xml.XmlReader.Create(be.ProjectFileOfTaskNode)
-  let project = new Project(reader)
-  project.AllEvaluatedProperties
-  |> Seq.filter (fun x -> not x.IsEnvironmentProperty)
-  |> Seq.filter (fun x -> not x.IsGlobalProperty)
-  |> Seq.filter (fun x -> not x.IsReservedProperty)
-  |> Seq.map (fun x -> (x.Name, x.EvaluatedValue))
-  |> Map.ofSeq
-
-/// Convert an absolute windows path to an msys path
-let mungePath path =
-  let matchre pat str =
-    let m = System.Text.RegularExpressions.Regex.Match(str, pat)
-    if m.Success
-    then List.tail [ for g in m.Groups -> g.Value ]
-    else []
-  match matchre "([A-Za-z]):\\\\(.*)" path with
-  | drive :: path :: [] -> sprintf "/%s/%s" drive (path.Replace('\\', '/'))
-  | _ -> path
-
-type ShellWrapper(conf : ITaskItem) =
-  inherit ToolTask()
-
-  member val Arguments = "" with get, set
-  member val WorkingDirectory = "" with get, set
-
-  // ToolTask overrides
-  override val ToolName = "sh.exe" with get
-  override this.GenerateFullPathToTool() = conf.GetMetadata "Sh"
-  override this.GenerateCommandLineCommands() = this.Arguments
-  override this.GetWorkingDirectory() = this.WorkingDirectory
-
-  override this.Execute() =
-    if this.GenerateFullPathToTool() |> IO.File.Exists |> not then
-      failwith "sh.exe not found. Make sure the MSYS root is set to a correct location."
-
-    if not <| IO.Directory.Exists this.WorkingDirectory then ignore <| IO.Directory.CreateDirectory this.WorkingDirectory
-
-    this.UseCommandProcessor <- false
-    this.StandardOutputImportance <- "High"
-    this.EnvironmentVariables <- [| for x in ["CC"; "CPP"; "CFLAGS"; "PATH"; "INCLUDE"; "LIB"]
-                                    -> sprintf "%s=%s" <| x <| conf.GetMetadata(x).Replace("\n", "").Replace("        ", "") |]
-    base.Execute()
-
-type ExecShellScript() =
-  inherit Task()
-
-  // Task arguments
-  [<Required>] member val WorkingDirectory = "" with get, set
-  [<Required>] member val Command = "" with get, set
-  [<Required>] member val Configuration : ITaskItem = null with get, set
-  member val Arguments = "" with get, set
-
-  member private this.realArgs () =
-    let cleanArgs = this.Arguments.Replace("\r", "").Replace('\n', ' ')
-    sprintf "-c '%s %s'" (mungePath this.Command) cleanArgs
-
-  override this.Execute() =
-    try
-      let sw = ShellWrapper(this.Configuration,
-                            BuildEngine = this.BuildEngine,
-                            HostObject = this.HostObject,
-                            Arguments = this.realArgs(),
-                            WorkingDirectory = this.WorkingDirectory)
-
-      sw.Execute()
-    with
-    | Failure(e) -> this.Log.LogError(e); false
-    | e -> this.Log.LogErrorFromException(e); false
-
-type MsysPath() =
-  inherit Task()
-
-  member val Path = "" with get, set
-  [<Output>] member val Result = "" with get, set
-
-  override this.Execute() =
-    try
-      this.Result <- mungePath this.Path
-      true
-    with Failure(e) ->
-      this.Log.LogError(e)
-      false
-
-type UpdateFile() =
-  inherit Task()
-
-  member val File = "" with get, set
-  member val Find = "" with get, set
-  member val Replacement = "" with get, set
-
-  override this.Execute() =
-    try
-      this.Log.LogMessage("Replacing '{0}' with '{1}' in '{2}'", this.Find, this.Replacement, this.File)
-      let text = IO.File.ReadAllText(this.File).Replace(this.Find, this.Replacement)
-      IO.File.WriteAllText(this.File, text)
-      true
-    with e ->
-      this.Log.LogErrorFromException e
-      false
diff --git a/aegisub/build/BuildTasks/DependencyFetchers.fs b/aegisub/build/BuildTasks/DependencyFetchers.fs
deleted file mode 100644
index 5827acffad49628de3fb4716a8916c8fd87f7deb..0000000000000000000000000000000000000000
--- a/aegisub/build/BuildTasks/DependencyFetchers.fs
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright (c) 2012, Thomas Goyne <plorkyeran@aegisub.org>
-//
-// Permission to use, copy, modify, and distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-//
-// Aegisub Project http://www.aegisub.org/
-
-module DependencyFetchers
-
-open System
-open System.Diagnostics
-open System.Linq
-open Microsoft.Build.Evaluation
-open Microsoft.Build.Framework
-open Microsoft.Build.Utilities
-
-let downloadArchive (url : String) unpackDest =
-  use wc = new Net.WebClient()
-  use downloadStream = wc.OpenRead url
-  use gzStream = new ICSharpCode.SharpZipLib.GZip.GZipInputStream(downloadStream)
-  use tarStream = new ICSharpCode.SharpZipLib.Tar.TarInputStream(gzStream)
-  use tarArchive = ICSharpCode.SharpZipLib.Tar.TarArchive.CreateInputTarArchive tarStream
-  tarArchive.ExtractContents unpackDest
-
-type DownloadTgzFile() =
-  inherit Task()
-
-  member val Url = "" with get, set
-  member val Destination = "" with get, set
-  member val OutputFile = "" with get, set
-  member val Hash = "" with get, set
-
-  override this.Execute() =
-    let needsDownload =
-      try
-        use fs = IO.File.OpenRead this.OutputFile
-        let sha = new Security.Cryptography.SHA1Managed ()
-        let hash = sha.ComputeHash fs
-        BitConverter.ToString(hash).Replace("-", "") <> this.Hash
-      with | :? IO.IOException -> true
-
-    try
-      if needsDownload
-      then downloadArchive this.Url this.Destination
-      true
-    with e ->
-      this.Log.LogErrorFromException e
-      false
diff --git a/aegisub/build/BuildTasks/DownloadTgzFile.cs b/aegisub/build/BuildTasks/DownloadTgzFile.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f829018bf622a2cc531c3fb330a76cf6ad6c5f51
--- /dev/null
+++ b/aegisub/build/BuildTasks/DownloadTgzFile.cs
@@ -0,0 +1,55 @@
+// Copyright (c) 2014, Thomas Goyne <plorkyeran@aegisub.org>
+//
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+//
+// Aegisub Project http://www.aegisub.org/
+
+namespace BuildTasks {
+    public class DownloadTgzFile : Microsoft.Build.Utilities.Task {
+        public string Url { get; set; }
+        public string Destination { get; set; }
+        public string OutputFile { get; set; }
+        public string Hash { get; set; }
+
+        private void DownloadArchive(string url, string unpackDest) {
+            var downloadStream = new System.Net.WebClient().OpenRead(url);
+            var gzStream = new ICSharpCode.SharpZipLib.GZip.GZipInputStream(downloadStream);
+            var tarStream = new ICSharpCode.SharpZipLib.Tar.TarInputStream(gzStream);
+            var tarArchive = ICSharpCode.SharpZipLib.Tar.TarArchive.CreateInputTarArchive(tarStream);
+            tarArchive.ExtractContents(unpackDest);
+        }
+
+        public override bool Execute() {
+            try {
+                using (var fs = System.IO.File.OpenRead(this.OutputFile)) {
+                    var hash = new System.Security.Cryptography.SHA1Managed().ComputeHash(fs);
+                    if (System.BitConverter.ToString(hash).Replace("-", "").ToLower() == this.Hash)
+                        return true;
+                }
+            }
+            catch (System.IO.IOException) {
+                // Need to download if file not present or not readable
+            }
+
+            try {
+                DownloadArchive(this.Url, this.Destination);
+            }
+            catch (System.Exception e) {
+                this.Log.LogErrorFromException(e);
+                return false;
+            }
+
+            return true;
+        }
+    }
+}
diff --git a/aegisub/build/BuildTasks/ExecShellScript.cs b/aegisub/build/BuildTasks/ExecShellScript.cs
new file mode 100644
index 0000000000000000000000000000000000000000..aabc5fa537fc064902d21f65a90fe836660592f2
--- /dev/null
+++ b/aegisub/build/BuildTasks/ExecShellScript.cs
@@ -0,0 +1,48 @@
+// Copyright (c) 2014, Thomas Goyne <plorkyeran@aegisub.org>
+//
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+//
+// Aegisub Project http://www.aegisub.org/
+
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+using System;
+
+namespace BuildTasks {
+    public class ExecShellScript : Task {
+        [Required] public string WorkingDirectory { get; set; }
+        [Required] public string Command { get; set; }
+        [Required] public ITaskItem Configuration { get; set; }
+        public string Arguments { get; set; }
+
+        private string RealArgs() {
+            return string.Format("-c '{0} {1}'", Utils.MungePath(this.Command),
+                (this.Arguments ?? "").Replace("\r", "").Replace('\n', ' '));
+        }
+
+        public override bool Execute() {
+            try {
+                var sw = new ShellWrapper(this.Configuration);
+                sw.BuildEngine = this.BuildEngine;
+                sw.HostObject = this.HostObject;
+                sw.Arguments = this.RealArgs();
+                sw.WorkingDirectory = this.WorkingDirectory;
+                return sw.Execute();
+            }
+            catch (Exception e) {
+                this.Log.LogErrorFromException(e);
+                return false;
+            }
+        }
+    }
+}
diff --git a/aegisub/build/BuildTasks/MsysPath.cs b/aegisub/build/BuildTasks/MsysPath.cs
new file mode 100644
index 0000000000000000000000000000000000000000..a87c590873d92be0d25fdd6f4182001d573f1b65
--- /dev/null
+++ b/aegisub/build/BuildTasks/MsysPath.cs
@@ -0,0 +1,37 @@
+// Copyright (c) 2014, Thomas Goyne <plorkyeran@aegisub.org>
+//
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+//
+// Aegisub Project http://www.aegisub.org/
+
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+using System;
+
+namespace BuildTasks {
+    public class MsysPath : Task {
+        public string Path { get; set;  }
+        [Output] public string Result { get; set; }
+
+        public override bool Execute() {
+            try {
+                this.Result = Utils.MungePath(this.Path);
+                return true;
+            }
+            catch (Exception e) {
+                this.Log.LogErrorFromException(e);
+                return false;
+            }
+        }
+    }
+}
diff --git a/aegisub/build/BuildTasks/Properties/AssemblyInfo.cs b/aegisub/build/BuildTasks/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000000000000000000000000000000000..5de3501173c1c3cbd7df5e004f74c90f61dbacee
--- /dev/null
+++ b/aegisub/build/BuildTasks/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following 
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("BuildTasks")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("BuildTasks")]
+[assembly: AssemblyCopyright("Copyright © Thomas Goyne 2014")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible 
+// to COM components.  If you need to access a type in this assembly from 
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("157803fb-ca11-4802-8394-1ee152112561")]
+
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version 
+//      Build Number
+//      Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers 
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/aegisub/build/BuildTasks/ShellWrapper.cs b/aegisub/build/BuildTasks/ShellWrapper.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c656c064c54e530720ffe37d62f0eed4bf5908ce
--- /dev/null
+++ b/aegisub/build/BuildTasks/ShellWrapper.cs
@@ -0,0 +1,55 @@
+// Copyright (c) 2014, Thomas Goyne <plorkyeran@aegisub.org>
+//
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+//
+// Aegisub Project http://www.aegisub.org/
+
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+using System;
+using System.IO;
+using System.Linq;
+
+namespace BuildTasks {
+    public class ShellWrapper : ToolTask {
+        private ITaskItem _conf;
+
+        public string Arguments { get; set; }
+        public string WorkingDirectory { get; set; }
+
+        protected override string ToolName { get { return "sh.exe"; } }
+        protected override string GenerateFullPathToTool() { return _conf.GetMetadata("Sh"); }
+        protected override string GenerateCommandLineCommands() { return this.Arguments; }
+        protected override string GetWorkingDirectory() { return this.WorkingDirectory; }
+
+        public ShellWrapper(ITaskItem conf) {
+            _conf = conf;
+        }
+
+        public override bool Execute() {
+            if (!File.Exists(this.GenerateFullPathToTool()))
+                throw new Exception("sh.exe not found. Make sure the MSYS root is set to a correct location.");
+            if (!Directory.Exists(this.WorkingDirectory))
+                Directory.CreateDirectory(this.WorkingDirectory);
+
+            this.UseCommandProcessor = false;
+            this.StandardOutputImportance = "High";
+            this.EnvironmentVariables =
+                new string[] {"CC", "CPP", "CFLAGS", "PATH", "INCLUDE", "LIB"}
+                .Select(x => string.Format("{0}={1}", x, _conf.GetMetadata(x).Replace("\n", "").Replace("        ", "")))
+                .ToArray();
+
+            return base.Execute();
+        }
+    }
+}
diff --git a/aegisub/build/BuildTasks/UpdateFile.cs b/aegisub/build/BuildTasks/UpdateFile.cs
new file mode 100644
index 0000000000000000000000000000000000000000..82a08392215767bbcb0385bf910c010dfa888570
--- /dev/null
+++ b/aegisub/build/BuildTasks/UpdateFile.cs
@@ -0,0 +1,40 @@
+// Copyright (c) 2014, Thomas Goyne <plorkyeran@aegisub.org>
+//
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+//
+// Aegisub Project http://www.aegisub.org/
+
+using Microsoft.Build.Utilities;
+using System;
+
+namespace BuildTasks {
+    public class UpdateFile : Task {
+        public string File { get; set; }
+        public string Find { get; set; }
+        public string Replacement { get; set; }
+
+        public override bool Execute() {
+            try {
+              this.Log.LogMessage("Replacing '{0}' with '{1}' in '{2}'",
+                  this.Find, this.Replacement, this.File);
+              var text = System.IO.File.ReadAllText(this.File).Replace(this.Find, this.Replacement);
+              System.IO.File.WriteAllText(this.File, text);
+              return true;
+            }
+            catch (Exception e) {
+                this.Log.LogErrorFromException(e);
+                return false;
+            }
+        }
+    }
+}
diff --git a/aegisub/build/BuildTasks/Utils.cs b/aegisub/build/BuildTasks/Utils.cs
new file mode 100644
index 0000000000000000000000000000000000000000..d69b52e2ff4d0f535e5e01ebb05ab09d903f45cf
--- /dev/null
+++ b/aegisub/build/BuildTasks/Utils.cs
@@ -0,0 +1,43 @@
+// Copyright (c) 2014, Thomas Goyne <plorkyeran@aegisub.org>
+//
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+//
+// Aegisub Project http://www.aegisub.org/
+
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Framework;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.RegularExpressions;
+using System.Xml;
+
+namespace BuildTasks {
+    static class Utils {
+        static public Dictionary<string, string> ReadPropertyMap(IBuildEngine be) {
+            var reader = XmlReader.Create(be.ProjectFileOfTaskNode);
+            return new Project(reader).AllEvaluatedProperties
+                .Where(x => !x.IsEnvironmentProperty)
+                .Where(x => !x.IsGlobalProperty)
+                .Where(x => !x.IsReservedProperty)
+                .ToDictionary(x => x.Name, x => x.EvaluatedValue);
+        }
+
+        /// Convert an absolute windows path to an msys path
+        static public string MungePath(string path) {
+            var match = Regex.Match(path, @"([A-Za-z]):\\(.*)");
+            if (!match.Success)
+                return path;
+            return string.Format("/{0}/{1}", match.Groups[1].Value, match.Groups[2].Value.Replace('\\', '/'));
+        }
+    }
+}