From 31be780fe1c86961c1b3d69f90233e917da22bff Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Mon, 18 Aug 2014 12:46:57 -0700 Subject: [PATCH 01/19] Update Setup.rc to be Squirrel-aware so that we can test detecting it --- src/Setup/Setup.rc | Bin 4922 -> 6900 bytes src/Setup/resource.h | 3 ++- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Setup/Setup.rc b/src/Setup/Setup.rc index 8c1fd66951a7a192e3df6c1f2c78e7228de73162..0b19cc1a3aa085609cd7644a4df29a1efc904adb 100644 GIT binary patch literal 6900 zcmdU!S#Kgo5Xb8|Qoh3kzhspSYwT5|ta8XUm_@;W%#lNukk=T;5@5m+Z=!tmB)@+* zz0AN2YwUf%8V%jkU0q#wRX6|r`^sKfWM_73S2nRboAUN;##o23sm<-u27H~^S3Hs3 z*tLy#CydW%dyGwJwQf%DihjhGu-)1in7n;%pRvm;*X}VoVs-#@&%x%_*5~ba%QDR1kzcSL~_sotNsnQSoi-z5M0(GH{tc+Y% zW!y(fcQ%Co8Pbs)T5$afzZJMXfltY!#P|X4sz%mz&}Rq9zYp}fM1`}1^nLcO zeQd9wi`}ypQ6%d2@SjZ@e27o%K9}M)!UjiJ*#KQnv9HL!#MVcAh4|B?=cCcoUZhz6 z)Je=sP4*bs7m1kC1LKjM;!Qr5G_m|PQRRf4yI5qEM|l6?i85EVz;TZ6c3D+p{R_8i zmC+ibZQ3$35xD$_hb!}4Ax6Emm&`SRJI;jYGV7z+^Uz$NGJ?Y^e01a_5-fOqHo}Xg zRq1%(d|G)$xk!;ABE~2e&0YK$)4O!pMj1%iVX}aou`+VeN7+k}FamPSBMZ86mU8Q; ziQIRbE+09Ez$i{lS?TGX@-0dfmqm}(2OkwsUQ#t$E< z@oo2sS8hSy@`8ueiNOb+lUEmslgi$rJ@~u&ilpbn@HQt752JeEH^$SWdB78-_OUkS zqIVCUQD({8-}vS@cJg^nGVH*CGO*9AtCH-Ht-72jO31T`{FLdsE&`s?*I6?@qsXXn zVTtHf$YG@wn20F*lGy0q!UDDd{yMs9lTBL>S`o0Hx~i)Tb;_DePCgOX-`gS4vg)w= z$Qd%5>cSF{yiFue(z(RsF%&+mzouH4BB#yp`!V>53dkk;Kwu1Logd?Sffob21T zj-z+{CbQ0ROZk2wkb`cWp2zswh-$4yJA~q#tGaX8imaw?Z{Y2_71)Rs z9{Pt2deV0cQYo@e{655cNczrUjX!0quX-E>N#6`R7l=6fi?^^9XVSAG*2GC>uqMrT zWqOSdNzUGagQSveQd3pnZ;<%F>0ykvWM?;=3T7YlC2b~WbSgY?r?dmODqu(3>{(yz zA$!)&K@UOmL1s_mtidjK)E0A26|*&|C2cSX`&b)HV)@i|(6m3`MJF$vvIh9uJ;PCyrg#7 z)4bm2l*8jOU8jpndxt!HzKYjHx_v!&*}F{d$lX|!9JlGQUUii6zwQ|uw7Rowkxi7H z(w_L)Hfb>dlkRF1uYbmil$CV*s~eluvu@5sjTy1xjnl+E7J5(28=~RleRtlS^oKy{ zJn}VmJ*Dr@0VAYvhctR_M5k`u4=9)C=}p1@-hO1HXWwHf2lfrWhb!?%l%K$R6Rvf4 zonApMr6CyvNH2q=ZDm*KpXRn~x9v7~m)dPlw9m~epZ0HnPK}%Y4G6N_ZH;H!`v~9E Mo!mqI#SpLm1N!hg7XSbN delta 20 ccmexjx=U??AMfM_cCN{Oyni Date: Mon, 18 Aug 2014 13:16:45 -0700 Subject: [PATCH 02/19] Add what will eventually become Update.exe but now currently is just a dummy Squirrel-aware EXE --- Squirrel.sln | 35 ++++++++++++++++ src/Update/App.config | 6 +++ src/Update/Program.cs | 15 +++++++ src/Update/Properties/AssemblyInfo.cs | 37 +++++++++++++++++ src/Update/Update.csproj | 58 +++++++++++++++++++++++++++ 5 files changed, 151 insertions(+) create mode 100644 src/Update/App.config create mode 100644 src/Update/Program.cs create mode 100644 src/Update/Properties/AssemblyInfo.cs create mode 100644 src/Update/Update.csproj diff --git a/Squirrel.sln b/Squirrel.sln index 1f2218f8..27a7ede6 100644 --- a/Squirrel.sln +++ b/Squirrel.sln @@ -14,30 +14,65 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{50DF07 .nuget\packages.config = .nuget\packages.config EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Update", "src\Update\Update.csproj", "{1EEBACBC-6982-4696-BD4E-899ED0AC6CD2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution CIBuild|Any CPU = CIBuild|Any CPU + CIBuild|Mixed Platforms = CIBuild|Mixed Platforms Debug|Any CPU = Debug|Any CPU + Debug|Mixed Platforms = Debug|Mixed Platforms Release|Any CPU = Release|Any CPU + Release|Mixed Platforms = Release|Mixed Platforms EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {1436E22A-FE3C-4D68-9A85-9E74DF2E6A92}.CIBuild|Any CPU.ActiveCfg = CIBuild|Any CPU {1436E22A-FE3C-4D68-9A85-9E74DF2E6A92}.CIBuild|Any CPU.Build.0 = CIBuild|Any CPU + {1436E22A-FE3C-4D68-9A85-9E74DF2E6A92}.CIBuild|Mixed Platforms.ActiveCfg = CIBuild|Any CPU + {1436E22A-FE3C-4D68-9A85-9E74DF2E6A92}.CIBuild|Mixed Platforms.Build.0 = CIBuild|Any CPU {1436E22A-FE3C-4D68-9A85-9E74DF2E6A92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1436E22A-FE3C-4D68-9A85-9E74DF2E6A92}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1436E22A-FE3C-4D68-9A85-9E74DF2E6A92}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {1436E22A-FE3C-4D68-9A85-9E74DF2E6A92}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU {1436E22A-FE3C-4D68-9A85-9E74DF2E6A92}.Release|Any CPU.ActiveCfg = Release|Any CPU {1436E22A-FE3C-4D68-9A85-9E74DF2E6A92}.Release|Any CPU.Build.0 = Release|Any CPU + {1436E22A-FE3C-4D68-9A85-9E74DF2E6A92}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {1436E22A-FE3C-4D68-9A85-9E74DF2E6A92}.Release|Mixed Platforms.Build.0 = Release|Any CPU {98AEB048-E27D-42F4-9440-505B7F78BAFD}.CIBuild|Any CPU.ActiveCfg = CIBuild|Any CPU {98AEB048-E27D-42F4-9440-505B7F78BAFD}.CIBuild|Any CPU.Build.0 = CIBuild|Any CPU + {98AEB048-E27D-42F4-9440-505B7F78BAFD}.CIBuild|Mixed Platforms.ActiveCfg = CIBuild|Any CPU + {98AEB048-E27D-42F4-9440-505B7F78BAFD}.CIBuild|Mixed Platforms.Build.0 = CIBuild|Any CPU {98AEB048-E27D-42F4-9440-505B7F78BAFD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {98AEB048-E27D-42F4-9440-505B7F78BAFD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {98AEB048-E27D-42F4-9440-505B7F78BAFD}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {98AEB048-E27D-42F4-9440-505B7F78BAFD}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU {98AEB048-E27D-42F4-9440-505B7F78BAFD}.Release|Any CPU.ActiveCfg = Release|Any CPU {98AEB048-E27D-42F4-9440-505B7F78BAFD}.Release|Any CPU.Build.0 = Release|Any CPU + {98AEB048-E27D-42F4-9440-505B7F78BAFD}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {98AEB048-E27D-42F4-9440-505B7F78BAFD}.Release|Mixed Platforms.Build.0 = Release|Any CPU {C1D40624-A484-438A-B846-052F321C89D1}.CIBuild|Any CPU.ActiveCfg = CIBuild|Win32 + {C1D40624-A484-438A-B846-052F321C89D1}.CIBuild|Mixed Platforms.ActiveCfg = CIBuild|Win32 + {C1D40624-A484-438A-B846-052F321C89D1}.CIBuild|Mixed Platforms.Build.0 = CIBuild|Win32 {C1D40624-A484-438A-B846-052F321C89D1}.Debug|Any CPU.ActiveCfg = Debug|Win32 {C1D40624-A484-438A-B846-052F321C89D1}.Debug|Any CPU.Build.0 = Debug|Win32 + {C1D40624-A484-438A-B846-052F321C89D1}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 + {C1D40624-A484-438A-B846-052F321C89D1}.Debug|Mixed Platforms.Build.0 = Debug|Win32 {C1D40624-A484-438A-B846-052F321C89D1}.Release|Any CPU.ActiveCfg = Release|Win32 {C1D40624-A484-438A-B846-052F321C89D1}.Release|Any CPU.Build.0 = Release|Win32 + {C1D40624-A484-438A-B846-052F321C89D1}.Release|Mixed Platforms.ActiveCfg = Release|Win32 + {C1D40624-A484-438A-B846-052F321C89D1}.Release|Mixed Platforms.Build.0 = Release|Win32 + {1EEBACBC-6982-4696-BD4E-899ED0AC6CD2}.CIBuild|Any CPU.ActiveCfg = Release|Any CPU + {1EEBACBC-6982-4696-BD4E-899ED0AC6CD2}.CIBuild|Any CPU.Build.0 = Release|Any CPU + {1EEBACBC-6982-4696-BD4E-899ED0AC6CD2}.CIBuild|Mixed Platforms.ActiveCfg = Release|Any CPU + {1EEBACBC-6982-4696-BD4E-899ED0AC6CD2}.CIBuild|Mixed Platforms.Build.0 = Release|Any CPU + {1EEBACBC-6982-4696-BD4E-899ED0AC6CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1EEBACBC-6982-4696-BD4E-899ED0AC6CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1EEBACBC-6982-4696-BD4E-899ED0AC6CD2}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {1EEBACBC-6982-4696-BD4E-899ED0AC6CD2}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {1EEBACBC-6982-4696-BD4E-899ED0AC6CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1EEBACBC-6982-4696-BD4E-899ED0AC6CD2}.Release|Any CPU.Build.0 = Release|Any CPU + {1EEBACBC-6982-4696-BD4E-899ED0AC6CD2}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {1EEBACBC-6982-4696-BD4E-899ED0AC6CD2}.Release|Mixed Platforms.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Update/App.config b/src/Update/App.config new file mode 100644 index 00000000..8e156463 --- /dev/null +++ b/src/Update/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/Update/Program.cs b/src/Update/Program.cs new file mode 100644 index 00000000..306c98f3 --- /dev/null +++ b/src/Update/Program.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Update +{ + class Program + { + static void Main(string[] args) + { + } + } +} diff --git a/src/Update/Properties/AssemblyInfo.cs b/src/Update/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..06e21d1e --- /dev/null +++ b/src/Update/Properties/AssemblyInfo.cs @@ -0,0 +1,37 @@ +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("Update")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Update")] +[assembly: AssemblyCopyright("Copyright © 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("a0663459-8b01-4fcd-96b8-c4235eb4dc51")] + +// 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")] +[assembly: AssemblyMetadata("SquirrelAwareVersion", "1")] diff --git a/src/Update/Update.csproj b/src/Update/Update.csproj new file mode 100644 index 00000000..03c7fb8f --- /dev/null +++ b/src/Update/Update.csproj @@ -0,0 +1,58 @@ + + + + + Debug + AnyCPU + {1EEBACBC-6982-4696-BD4E-899ED0AC6CD2} + Exe + Properties + Update + Update + v4.5 + 512 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 36c22e00f196d9e08525df2ca3e30d7622540f57 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Mon, 18 Aug 2014 14:02:53 -0700 Subject: [PATCH 03/19] Add a class to find Squirrel-aware PE images --- src/Squirrel/NativeMethods.cs | 27 +++++++ src/Squirrel/Squirrel.csproj | 2 + .../SquirrelAwareExecutableDetector.cs | 73 +++++++++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 src/Squirrel/NativeMethods.cs create mode 100644 src/Squirrel/SquirrelAwareExecutableDetector.cs diff --git a/src/Squirrel/NativeMethods.cs b/src/Squirrel/NativeMethods.cs new file mode 100644 index 00000000..611f4d3b --- /dev/null +++ b/src/Squirrel/NativeMethods.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Squirrel +{ + static class NativeMethods + { + [DllImport("version.dll", SetLastError = true)] + public static extern bool GetFileVersionInfo( + string lpszFileName, + IntPtr dwHandleIgnored, + int dwLen, + [MarshalAs(UnmanagedType.LPArray)] byte[] lpData); + + [DllImport("version.dll", SetLastError = true)] + public static extern int GetFileVersionInfoSize( + string lpszFileName, + IntPtr dwHandleIgnored); + + [DllImport("version.dll")] + public static extern bool VerQueryValue(byte[] pBlock, string pSubBlock, out IntPtr pValue, out int len); + } +} diff --git a/src/Squirrel/Squirrel.csproj b/src/Squirrel/Squirrel.csproj index 1adfd660..48ab0205 100644 --- a/src/Squirrel/Squirrel.csproj +++ b/src/Squirrel/Squirrel.csproj @@ -71,12 +71,14 @@ + + diff --git a/src/Squirrel/SquirrelAwareExecutableDetector.cs b/src/Squirrel/SquirrelAwareExecutableDetector.cs new file mode 100644 index 00000000..a99d8d22 --- /dev/null +++ b/src/Squirrel/SquirrelAwareExecutableDetector.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Squirrel +{ + static class SquirrelAwareExecutableDetector + { + public static int? GetPESquirrelAwareVersion(string executable) + { + if (!File.Exists(executable)) return null; + var fullname = Path.GetFullPath(executable); + + return GetAssemblySquirrelAwareVersion(fullname) ?? GetVersionBlockSquirrelAwareValue(fullname); + } + + static int? GetAssemblySquirrelAwareVersion(string executable) + { + try + { + var assembly = Assembly.ReflectionOnlyLoadFrom(executable); + var attrs = assembly.GetCustomAttributesData(); + var attribute = attrs.FirstOrDefault(x => + { + if (x.AttributeType != typeof(AssemblyMetadataAttribute)) return false; + if (x.ConstructorArguments.Count != 2) return false; + return x.ConstructorArguments[0].Value.ToString() == "SquirrelAwareVersion"; + }); + + if (attribute == null) return null; + + int result; + if (!Int32.TryParse(attribute.ConstructorArguments[1].Value.ToString(), NumberStyles.Integer, CultureInfo.CurrentCulture, out result)) + { + return null; + } + + return result; + } + catch (FileLoadException) { return null; } + catch (BadImageFormatException) { return null; } + } + + static int? GetVersionBlockSquirrelAwareValue(string executable) + { + int size = NativeMethods.GetFileVersionInfoSize(executable, IntPtr.Zero); + + // Nice try, buffer overflow + if (size <= 0 || size > 4096) return null; + + var buf = new byte[size]; + if (!NativeMethods.GetFileVersionInfo(executable, IntPtr.Zero, size, buf)) return null; + + IntPtr result; int resultSize; + if (!NativeMethods.VerQueryValue(buf, "\\StringFileInfo\\040904B0\\SquirrelAwareVersion", out result, out resultSize)) + { + return null; + } + + int ret; + string resultData = Marshal.PtrToStringAnsi(result, resultSize-1 /* Subtract one for null terminator */); + if (!Int32.TryParse(resultData, NumberStyles.Integer, CultureInfo.CurrentCulture, out ret)) return null; + + return ret; + } + } +} From 52c356f93de86c5d4ace9bc65e6e70a05a595b11 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Mon, 18 Aug 2014 14:03:11 -0700 Subject: [PATCH 04/19] Make sure our code works --- test/Squirrel.Tests.csproj | 5 ++ test/SquirrelAwareExecutableDetectorTests.cs | 53 ++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 test/SquirrelAwareExecutableDetectorTests.cs diff --git a/test/Squirrel.Tests.csproj b/test/Squirrel.Tests.csproj index 28580b65..a9e15cfa 100644 --- a/test/Squirrel.Tests.csproj +++ b/test/Squirrel.Tests.csproj @@ -84,6 +84,7 @@ + @@ -98,6 +99,10 @@ {1436e22a-fe3c-4d68-9a85-9e74df2e6a92} Squirrel + + {1eebacbc-6982-4696-bd4e-899ed0ac6cd2} + Update + diff --git a/test/SquirrelAwareExecutableDetectorTests.cs b/test/SquirrelAwareExecutableDetectorTests.cs new file mode 100644 index 00000000..8630e4fc --- /dev/null +++ b/test/SquirrelAwareExecutableDetectorTests.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using Squirrel.Tests.TestHelpers; +using Xunit; + +namespace Squirrel.Tests +{ + public class SquirrelAwareExecutableDetectorTests + { + [Fact] + public void SquirrelAwareViaVersionBlock() + { + var target = Path.Combine( + IntegrationTestHelper.GetIntegrationTestRootDirectory(), + "..", "src", "Setup", "bin", "Release", "Setup.exe"); + + Assert.True(File.Exists(target)); + + var ret = SquirrelAwareExecutableDetector.GetPESquirrelAwareVersion(target); + Assert.Equal(1, ret.Value); + } + + [Fact] + public void SquirrelAwareViaAssemblyAttribute() + { + var target = Path.Combine( + IntegrationTestHelper.GetIntegrationTestRootDirectory(), + "..", "src", "Update", "bin", "Release", "Update.exe"); + + Assert.True(File.Exists(target)); + + var ret = SquirrelAwareExecutableDetector.GetPESquirrelAwareVersion(target); + Assert.Equal(1, ret.Value); + } + + [Fact] + public void NotSquirrelAware() + { + var target = Assembly.GetExecutingAssembly().Location; + Assert.True(File.Exists(target)); + + var ret = SquirrelAwareExecutableDetector.GetPESquirrelAwareVersion(target); + Assert.Null(ret); + } + } +} From 1948c51a86c45930b23ac883fe1655b024caae73 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Tue, 19 Aug 2014 12:43:32 -0700 Subject: [PATCH 05/19] Get all the Squirrel-aware apps in a directory --- src/Squirrel/SquirrelAwareExecutableDetector.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Squirrel/SquirrelAwareExecutableDetector.cs b/src/Squirrel/SquirrelAwareExecutableDetector.cs index a99d8d22..f0ce48e1 100644 --- a/src/Squirrel/SquirrelAwareExecutableDetector.cs +++ b/src/Squirrel/SquirrelAwareExecutableDetector.cs @@ -12,6 +12,17 @@ namespace Squirrel { static class SquirrelAwareExecutableDetector { + public static List GetAllSquirrelAwareApps(string directory, int minimumVersion = 1) + { + var di = new DirectoryInfo(directory); + + return di.EnumerateFiles() + .Where(x => x.Name.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)) + .Select(x => x.FullName) + .Where(x => (GetPESquirrelAwareVersion(x) ?? -1) >= minimumVersion) + .ToList(); + } + public static int? GetPESquirrelAwareVersion(string executable) { if (!File.Exists(executable)) return null; From b384f4bdb1ad6da994065f9ecea057f330cfe1d7 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Tue, 19 Aug 2014 12:44:42 -0700 Subject: [PATCH 06/19] Implement FullUninstall --- src/Squirrel/UpdateManager.ApplyReleases.cs | 21 +++++++++------------ src/Squirrel/Utility.cs | 19 +++++++++++++++++++ 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/Squirrel/UpdateManager.ApplyReleases.cs b/src/Squirrel/UpdateManager.ApplyReleases.cs index 74200006..9aa23c3b 100644 --- a/src/Squirrel/UpdateManager.ApplyReleases.cs +++ b/src/Squirrel/UpdateManager.ApplyReleases.cs @@ -42,22 +42,19 @@ namespace Squirrel progress(100); } - public async Task FullUninstall(Version version = null) + public async Task FullUninstall() { - version = version ?? new Version(255, 255, 255, 255); - this.Log().Info("Uninstalling version '{0}'", version); + var currentRelease = getReleases().MaxBy(x => x.Name.ToVersion()).FirstOrDefault(); - // find all the old releases (and this one) - var directoriesToDelete = getOldReleases(version) - .Concat(new [] { getDirectoryForRelease(version) }) - .Where(d => d.Exists) - .Select(d => d.FullName); + if (currentRelease.Exists) + { + var version = currentRelease.Name.ToVersion(); - await directoriesToDelete.ForEachAsync(x => Utility.DeleteDirectoryWithFallbackToNextReboot(x)); - - if (!getReleases().Any()) { - await Utility.DeleteDirectoryWithFallbackToNextReboot(rootAppDirectory); + await SquirrelAwareExecutableDetector.GetAllSquirrelAwareApps(currentRelease.FullName) + .ForEachAsync(exe => Utility.InvokeProcessAsync(exe, String.Format("/squirrel-uninstall {0}", version)), 1); } + + await Utility.DeleteDirectoryWithFallbackToNextReboot(rootAppDirectory); } async Task installPackageToAppDir(UpdateInfo updateInfo, ReleaseEntry release) diff --git a/src/Squirrel/Utility.cs b/src/Squirrel/Utility.cs index 89c074ad..5f5d6c1e 100644 --- a/src/Squirrel/Utility.cs +++ b/src/Squirrel/Utility.cs @@ -14,6 +14,7 @@ using Splat; using System.Text; using System.Threading.Tasks; using System.Collections.Concurrent; +using System.Diagnostics; namespace Squirrel { @@ -98,6 +99,24 @@ namespace Squirrel } } + public static Task InvokeProcessAsync(string fileName, string arguments) + { + var psi = new ProcessStartInfo(fileName, arguments); + psi.UseShellExecute = false; + psi.WindowStyle = ProcessWindowStyle.Hidden; + psi.ErrorDialog = false; + + return InvokeProcessAsync(psi); + } + + public static async Task InvokeProcessAsync(ProcessStartInfo psi) + { + var pi = Process.Start(psi); + + await Task.Run(() => pi.WaitForExit()); + return pi.ExitCode; + } + public static Task ForEachAsync(this IEnumerable source, Action body, int degreeOfParallelism = 4) { return ForEachAsync(source, x => Task.Run(() => body(x)), degreeOfParallelism); From 6d88828d5e90bfc5f50711125c1deb78d28b25a1 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Tue, 19 Aug 2014 12:55:15 -0700 Subject: [PATCH 07/19] Implement post-install for install + update --- src/Squirrel/UpdateManager.ApplyReleases.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/Squirrel/UpdateManager.ApplyReleases.cs b/src/Squirrel/UpdateManager.ApplyReleases.cs index 9aa23c3b..310bf7fc 100644 --- a/src/Squirrel/UpdateManager.ApplyReleases.cs +++ b/src/Squirrel/UpdateManager.ApplyReleases.cs @@ -33,12 +33,16 @@ namespace Squirrel progress(10); await installPackageToAppDir(updateInfo, release); - progress(50); + progress(30); var currentReleases = await updateLocalReleasesFile(); + progress(50); + + var newVersion = currentReleases.MaxBy(x => x.Version).First().Version; + await invokePostInstall(newVersion, currentReleases.Count == 1); progress(75); - await cleanDeadVersions(currentReleases.MaxBy(x => x.Version).First().Version); + await cleanDeadVersions(newVersion); progress(100); } @@ -182,6 +186,17 @@ namespace Squirrel } } + async Task invokePostInstall(Version currentVersion, bool isInitialInstall) + { + var targetDir = getDirectoryForRelease(currentVersion); + var args = isInitialInstall ? + String.Format("/squirrel-install {0}", currentVersion) : + String.Format("/squirrel-updated {0}", currentVersion); + + await SquirrelAwareExecutableDetector.GetAllSquirrelAwareApps(targetDir.FullName) + .ForEachAsync(exe => Utility.InvokeProcessAsync(exe, args), 1); + } + void fixPinnedExecutables(Version newCurrentVersion) { if (Environment.OSVersion.Version < new Version(6, 1)) { From cc9866845ec05c56f0db9445a919eb6e8fec16f0 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Tue, 19 Aug 2014 13:05:30 -0700 Subject: [PATCH 08/19] On initial install, invoke all the apps with FirstRun --- src/Squirrel/UpdateManager.ApplyReleases.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Squirrel/UpdateManager.ApplyReleases.cs b/src/Squirrel/UpdateManager.ApplyReleases.cs index 310bf7fc..08efc6db 100644 --- a/src/Squirrel/UpdateManager.ApplyReleases.cs +++ b/src/Squirrel/UpdateManager.ApplyReleases.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.Contracts; using System.IO; using System.Linq; @@ -193,8 +194,14 @@ namespace Squirrel String.Format("/squirrel-install {0}", currentVersion) : String.Format("/squirrel-updated {0}", currentVersion); - await SquirrelAwareExecutableDetector.GetAllSquirrelAwareApps(targetDir.FullName) - .ForEachAsync(exe => Utility.InvokeProcessAsync(exe, args), 1); + var squirrelApps = SquirrelAwareExecutableDetector.GetAllSquirrelAwareApps(targetDir.FullName); + + // For each app, run the install command in-order and wait + await squirrelApps.ForEachAsync(exe => Utility.InvokeProcessAsync(exe, args), 1); + + // If this is the first run, we run the apps with first-run and + // *don't* wait for them, since they're probably the main EXE + squirrelApps.ForEach(exe => Process.Start(exe, "/squirrel-firstrun")); } void fixPinnedExecutables(Version newCurrentVersion) From b5b4472d3afdf0b588d3ea9f157f5bc7a303d71e Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Tue, 19 Aug 2014 13:08:09 -0700 Subject: [PATCH 09/19] If users didn't mark anything as Squirrel-aware, run them all --- src/Squirrel/UpdateManager.ApplyReleases.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Squirrel/UpdateManager.ApplyReleases.cs b/src/Squirrel/UpdateManager.ApplyReleases.cs index 08efc6db..6125ab16 100644 --- a/src/Squirrel/UpdateManager.ApplyReleases.cs +++ b/src/Squirrel/UpdateManager.ApplyReleases.cs @@ -199,8 +199,19 @@ namespace Squirrel // For each app, run the install command in-order and wait await squirrelApps.ForEachAsync(exe => Utility.InvokeProcessAsync(exe, args), 1); + if (!isInitialInstall) return; + // If this is the first run, we run the apps with first-run and // *don't* wait for them, since they're probably the main EXE + if (squirrelApps.Count == 0) { + this.Log().Warn("No apps are marked as Squirrel-aware! Going to run them all"); + + squirrelApps = targetDir.EnumerateFiles() + .Where(x => x.Name.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)) + .Select(x => x.FullName) + .ToList(); + } + squirrelApps.ForEach(exe => Process.Start(exe, "/squirrel-firstrun")); } From af63bdbbb1056e8f412c76565097ac1afb21dad0 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Wed, 20 Aug 2014 16:37:44 -0700 Subject: [PATCH 10/19] Add a test fixture that is Squirrel-aware This EXE just writes its arguments to a file in the same directory called "args.txt" --- test/SquirrelAwareExecutableDetectorTests.cs | 9 +++++++++ test/fixtures/SquirrelAwareApp.exe | Bin 0 -> 5120 bytes 2 files changed, 9 insertions(+) create mode 100644 test/fixtures/SquirrelAwareApp.exe diff --git a/test/SquirrelAwareExecutableDetectorTests.cs b/test/SquirrelAwareExecutableDetectorTests.cs index 8630e4fc..d74c7073 100644 --- a/test/SquirrelAwareExecutableDetectorTests.cs +++ b/test/SquirrelAwareExecutableDetectorTests.cs @@ -49,5 +49,14 @@ namespace Squirrel.Tests var ret = SquirrelAwareExecutableDetector.GetPESquirrelAwareVersion(target); Assert.Null(ret); } + + [Fact] + public void SquirrelAwareTestAppShouldBeSquirrelAware() + { + var target = IntegrationTestHelper.GetPath("fixtures", "SquirrelAwareApp.exe"); + Assert.True(File.Exists(target)); + + Assert.NotNull(SquirrelAwareExecutableDetector.GetPESquirrelAwareVersion(target)); + } } } diff --git a/test/fixtures/SquirrelAwareApp.exe b/test/fixtures/SquirrelAwareApp.exe new file mode 100644 index 0000000000000000000000000000000000000000..f50b07392139ba18dc22f3bad2762ed670ed6234 GIT binary patch literal 5120 zcmeHKO>7&-6@I&vWLb_GN&ZQkIQE!w5ZSd>|BmEJwrEk3X-Jl0(sG-~ke164wf1s% zy}Ojf{-i3>7AOidZVvr96ljCAfYD0}6lfB|E!tBq0g4>@b831E3KaIGNKh1UzZve5 z)Q=PQQn)~eoNwQ}_vX!;H?uQuXI{KXF(PU}y?>wRO*};zllaN74srbO+j08k&UcTz zsZ76nB)ekz%=Mgtrx%%_+qM(19A{q1W~R+jvl&)&@?6`!yX9zTdTxSfT8YuE?tf&W z(cULj*`@3vY5}LY!hQJ|o(y#iRRF_7t@sU!ngocljtaggFHCc6b2~)r zR8s1~Ds&#E?CFV79~9BItrO$7IC}t{djTGJZs$HwElnP{uG+a@$es}JBw+vXLu%&% z5Xa$Y=KNVjSV0kUS{=}OwEmv{-eJLMBnvo%Id;5E^ah?Mu!%b|foIwUUugUuYYCgw zaVbL|fW|JMj;TxO6rLZUy#&)8XDug(wkR;6Ozl%iEC>Db*?xj+Vsv2xGFe3%?$P6@ zf{rRYmb3`hIZ6V{v^S}e7OWKrpU1Aw{ zh`tIuiczz4oPGdor61Ez)o$Qzb%1WsAJrk?2kI#Of>Hy*wMkCgL&_kwhaiPQGOE)0DYhGCjP3$s4-Ildh}rHBNJtb8w50ioW4^ zmYJhW*$;S;W^-4$5zvfo+N2qQ<#8=SI`^I#H(EWhD+ z*OCGDeXbPnY8Sq2!6LLN>HEBxv&vaBuy{=|VyC!oc%~~QKbgqK@sbsk5Y{cT3 z6jm=~Cx=MHk1l&=z>}6Gb*N1zj|zD6Gzw@9m11AQn{?;Mm+zlD_lJqsUtPZMrhff8 zHUH`h3-bpC-v1^}spewUMbxJl+VhZ9*`bk+!tI0G)UP2?43`c)UsA zGvHABl^uB9Y0@EauxHi%mp$FR z;Mfyu2AA)4cExjU_|iYP80A{xoIeQWW1XnTwJv5-8SBa&=i|SBH1+n?zx?&P{Iyp^ zALG=*B_F5Hf~%LT1*|n+@WTUdVS{ft`L;5g+|>o_U#|Oni%@g(ISlYWQ0BrsNw{hY znh9!aiSntMWO3Z_@B+%yU7q}$Yk5oLFUYu`B{n9hM*~!uCo!a(BJaUXx4zM@N)|Eq z0RGB0e9?cC5pXIy@g}GrjUl%d8!;?5$BF({5lhZJWCcX?$k7*p6Lb+&8ac}aw9~*z z(4yRFxchO%Zq&K*JR_x*J3NwvEJ}e;U{#zNH%eKMpScf&Xtx=tln z_weKJB1t!-&m5GCpC~=}P^#IQhn@#(HD7yi?)Frx7<(V|#cTtqYr~?2c?fir ze2P{^wElD7QZmMKu<1}9Q8eI9ZI+)tPq+o&L~J+O*c{=_@g0(Vl2`*DaVcWOEkuN2 zw{2XR&He7){>{jlwW7sVE9u^70#`wT@$iZrP2hg*8cHxf(Cxf#IW`|nl)0Zcb9#5n zh#n?;3n6G@76Py22WflRc}Rcy60#r0_K~k3 zdnku(a6Ep!1V4Fx&NEk$T@<+gkXHL6s|=-PC(MZ)dz!Cui&+9k6S|+aSDkCzOR$oe zG>}drjLW*^^KeqqNcT40M6P#l@O`Aa8U!eebVnvj2E{RsH;e;59% G5%@Qh&uh8> literal 0 HcmV?d00001 From 71a22960fe3f58f83f55ff66254aac354b8508f5 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Wed, 20 Aug 2014 16:41:41 -0700 Subject: [PATCH 11/19] Just like SquirrelAwareApp only doesn't have the assembly attribute, and writes to args2.txt --- test/SquirrelAwareExecutableDetectorTests.cs | 10 ++++++++++ test/fixtures/NotSquirrelAwareApp.exe | Bin 0 -> 5120 bytes 2 files changed, 10 insertions(+) create mode 100644 test/fixtures/NotSquirrelAwareApp.exe diff --git a/test/SquirrelAwareExecutableDetectorTests.cs b/test/SquirrelAwareExecutableDetectorTests.cs index d74c7073..a7144f29 100644 --- a/test/SquirrelAwareExecutableDetectorTests.cs +++ b/test/SquirrelAwareExecutableDetectorTests.cs @@ -58,5 +58,15 @@ namespace Squirrel.Tests Assert.NotNull(SquirrelAwareExecutableDetector.GetPESquirrelAwareVersion(target)); } + + [Fact] + public void NotSquirrelAwareTestAppShouldNotBeSquirrelAware() + { + var target = IntegrationTestHelper.GetPath("fixtures", "NotSquirrelAwareApp.exe"); + Assert.True(File.Exists(target)); + + Assert.Null(SquirrelAwareExecutableDetector.GetPESquirrelAwareVersion(target)); + } + } } diff --git a/test/fixtures/NotSquirrelAwareApp.exe b/test/fixtures/NotSquirrelAwareApp.exe new file mode 100644 index 0000000000000000000000000000000000000000..65ca36dc2441d1b536a0f8acd31b010fd5c67b3a GIT binary patch literal 5120 zcmeHKU1%KF6+Sa7$%k_P(s1|p(Rd2sOd|gtxIWX=tB#k(3d>ac`1R?HvR6M z+0{y`B<@QwrFV9|Irp4%&pr3tzq51CT%#Bfwc!2uW1?I5$}*+!)8Q(_@rU1z(~lpx zbNH4vd*^U&)%ICgctv5Bm}R=I7qC2MqT({!W$F1WD|rQO?Aq1Q8<}31A)3`<^!vfT zWox~?Pr9~K+ey>`PJ75Tj^fMkPT`fn@X?4|&^c0%5M$E>~MWWB4e&Z3M1C9L8qFLQ_rP+@96Af*QfUgIrk30Z?OpP`28#PrJ z(M3b}!a_~?HU&ZsR65j9rr^CHxZ^=li7rAYZFS*oC{skI8mfxylyOCozUkCT-)69} zL&i=gts$&Z=Uz&8GDbsCMBCkcyD=;ePIUBj0|a>GzCEBi+QeSadLOW(Z=Zw*0s9_3 zr1w1p;!!x7ee#qht)R#;t&JE-V>mfHI4(J@(vt?+`vSG9GmjlQSwEyek* zLS4VBouqwmp^awrBYGQgg)ivR&Mo~WIPdAQ-@odA22HV>(%s0XFp8b@Fg=2}$=U*w z(r;qjOTS-MwQoF>LyW>vK0~`HM|)_$!ov!WDjZZet}vxAqwpz(=YjjlRJ5(|vcf9x z5WNWOMX$1-@6b#7QQ&pmpzHLmJ_P)=K1y%UpY%!kA$_E$=`CuFWiTI7-=Lke2<)cU zfM1}W0DBaEQQ@%Se+jWRXj*Zy3cpOh1LqR`6X;WiX3$rt8~6ep(4L}}tF;(WRItfO zo`kic(O8X~nDYu1hd)l)%N1K-x23L_f~U%5gRgU12+zDGA(ec~6ONsyY}F5ViRSaq zaVwxX({{pX`ZhXi{_@TLLR0Giv!+lnMF8PhH=s8Ef^}q z=eT38E6U$+DVl!S4x+s6*ugd-r!zd5S?5+IK*zP=5f;gXS0BRkv|joy(@P z-T!#@+0pNQkhh&q259Okc%C%{t4??lxe5hlm z(Gq2mQ7zF7odcCY=5iMG4DfWAz0KaJjfvXq_jrhc+?nJTa@@NjsMqASX04yuetmL&ctZP;3Zo`AItUk7nQC+n~5 zdoT3mXal9|!lHwCnp8raFrrWI(}32x(v<%lx?pP-wW6{zjR=*I1IvgMX%#&Xva+o_ zhHQ`F8G>JfBxgCNe43Bi$!JQSYBM8i|G95z72^fi^r(O+TJUCLl%G9LxCP&2Y&YB3 z8sV++9aC*mm;)biDPhJPM1)}tZ=RX0?e4zw(}|PoC5NqrDd1!R7e|8e=t7=M;5P0b zOE5n$-Gb?OE}u+Pxt}=s_^yr#GfDs%47mPeq9WWAzO{;6%kMAQmhgOUCFr-j(h1Wq z8Ebr8dNuVB(JgrbCF9-Cg!Thf*m_dJq)v4P7H(= z0#WgUjJx99qd!9l)ee38$Zn8pRHHgLf?uw{PhMCM_8M}LBKPmn>Tt~}L)owsWl)|w z%h$NW9Ep<&)6ckT-X$&)tYW7uq?icfis|?~8k90Ju#Go0*9SKFJ~2=a0u&|&YBPk2 ZTv_))Vd4Mlr21cdANccs7yhde_%|tmQriFk literal 0 HcmV?d00001 From 1518d1f0eac9375c4d32000c2ed6069b3a68e675 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Wed, 20 Aug 2014 17:15:57 -0700 Subject: [PATCH 12/19] Add a method to create fake squirrel-aware app packages at will --- test/TestHelpers/IntegrationTestHelper.cs | 43 +++++++++++++++++++++++ test/UtilityTests.cs | 10 ++++++ test/fixtures/SquirrelInstalledApp.nuspec | 14 ++++++++ 3 files changed, 67 insertions(+) create mode 100644 test/fixtures/SquirrelInstalledApp.nuspec diff --git a/test/TestHelpers/IntegrationTestHelper.cs b/test/TestHelpers/IntegrationTestHelper.cs index 09a2f59f..ac9b37ef 100644 --- a/test/TestHelpers/IntegrationTestHelper.cs +++ b/test/TestHelpers/IntegrationTestHelper.cs @@ -8,6 +8,7 @@ using Ionic.Zip; using Squirrel; using Splat; using Xunit; +using System.Text; namespace Squirrel.Tests.TestHelpers { @@ -69,6 +70,48 @@ namespace Squirrel.Tests.TestHelpers return ret; } + public static string CreateFakeInstalledApp(string version, string outputDir, string nuspecFile = null) + { + var targetDir = default(string); + + var nuget = IntegrationTestHelper.GetPath("..", ".nuget", "nuget.exe"); + nuspecFile = nuspecFile ?? "SquirrelInstalledApp.nuspec"; + + using (var clearTemp = Utility.WithTempDirectory(out targetDir)) { + var nuspec = File.ReadAllText(IntegrationTestHelper.GetPath("fixtures", nuspecFile), Encoding.UTF8); + File.WriteAllText(Path.Combine(targetDir, nuspecFile), nuspec.Replace("0.1.0", version), Encoding.UTF8); + + File.Copy( + IntegrationTestHelper.GetPath("fixtures", "SquirrelAwareApp.exe"), + Path.Combine(targetDir, "SquirrelAwareApp.exe")); + File.Copy( + IntegrationTestHelper.GetPath("fixtures", "NotSquirrelAwareApp.exe"), + Path.Combine(targetDir, "NotSquirrelAwareApp.exe")); + + var psi = new ProcessStartInfo(nuget, "pack " + Path.Combine(targetDir, nuspecFile)) { + RedirectStandardError = true, + RedirectStandardOutput = true, + UseShellExecute = false, + CreateNoWindow = true, + WorkingDirectory = targetDir, + WindowStyle = ProcessWindowStyle.Hidden, + }; + + var pi = Process.Start(psi); + pi.WaitForExit(); + var output = pi.StandardOutput.ReadToEnd(); + var err = pi.StandardError.ReadToEnd(); + Console.WriteLine(output); Console.WriteLine(err); + + var di = new DirectoryInfo(targetDir); + var pkg = di.EnumerateFiles("*.nupkg").First(); + + var targetPkgFile = Path.Combine(outputDir, pkg.Name); + File.Copy(pkg.FullName, targetPkgFile); + return targetPkgFile; + } + } + public static IDisposable WithFakeInstallDirectory(out string path) { return WithFakeInstallDirectory("SampleUpdatingApp.1.1.0.0.nupkg", out path); diff --git a/test/UtilityTests.cs b/test/UtilityTests.cs index 7b6d946b..7587c1be 100644 --- a/test/UtilityTests.cs +++ b/test/UtilityTests.cs @@ -52,6 +52,16 @@ namespace Squirrel.Tests.Core } } + [Fact] + public void CreateFakePackageSmokeTest() + { + string path; + using (Utility.WithTempDirectory(out path)) { + var output = IntegrationTestHelper.CreateFakeInstalledApp("0.3.0", path); + Assert.True(File.Exists(output)); + } + } + static void CreateSampleDirectory(string directory) { while (true) { diff --git a/test/fixtures/SquirrelInstalledApp.nuspec b/test/fixtures/SquirrelInstalledApp.nuspec new file mode 100644 index 00000000..3ac3e259 --- /dev/null +++ b/test/fixtures/SquirrelInstalledApp.nuspec @@ -0,0 +1,14 @@ + + + + SquirrelInstalledApp + 0.1.0 + paul + false + My package description. + + + + + + \ No newline at end of file From ae5472272bf8466eeb0fe7e7cec6b3aa5e0930fd Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Thu, 21 Aug 2014 14:11:57 -0700 Subject: [PATCH 13/19] Test that we invoke post-install command correctly --- test/ApplyReleasesTests.cs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test/ApplyReleasesTests.cs b/test/ApplyReleasesTests.cs index d2315374..3e7e2f1f 100644 --- a/test/ApplyReleasesTests.cs +++ b/test/ApplyReleasesTests.cs @@ -28,6 +28,32 @@ namespace Squirrel.Tests public class ApplyReleasesTests : IEnableLogger { + [Fact] + public async Task CleanInstallRunsSquirrelAwareAppsWithInstallFlag() + { + string tempDir; + string remotePkgDir; + + using (Utility.WithTempDirectory(out tempDir)) + using (Utility.WithTempDirectory(out remotePkgDir)) { + IntegrationTestHelper.CreateFakeInstalledApp("0.1.0", remotePkgDir); + var pkgs = ReleaseEntry.BuildReleasesFile(remotePkgDir); + ReleaseEntry.WriteReleaseFile(pkgs, Path.Combine(remotePkgDir, "RELEASES")); + + using (var fixture = new UpdateManager(remotePkgDir, "theApp", FrameworkVersion.Net45, tempDir)) { + await fixture.FullInstall(); + + // NB: We execute the Squirrel-aware apps, so we need to give + // them a minute to settle or else the using statement will + // try to blow away a running process + await Task.Delay(1000); + + Assert.False(File.Exists(Path.Combine(tempDir, "theApp", "app-0.1.0", "args2.txt"))); + Assert.True(File.Exists(Path.Combine(tempDir, "theApp", "app-0.1.0", "args.txt"))); + } + } + } + [Fact] public void WhenNoNewReleasesAreAvailableTheListIsEmpty() { From 0717de14a991a793e8f0773714dc4fd50b6f6695 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Thu, 21 Aug 2014 14:13:27 -0700 Subject: [PATCH 14/19] ReflectionLoadFile also loads into the AppDomain :man::gun: We need to examine the file without it being loaded into our AppDomain or else all kinds of badness happens. Instead, use Cecil to load the assembly --- src/Squirrel/Squirrel.csproj | 12 ++++++++++++ src/Squirrel/SquirrelAwareExecutableDetector.cs | 9 ++++++--- src/Squirrel/packages.config | 1 + 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/Squirrel/Squirrel.csproj b/src/Squirrel/Squirrel.csproj index 572ad9f2..cc40ff3c 100644 --- a/src/Squirrel/Squirrel.csproj +++ b/src/Squirrel/Squirrel.csproj @@ -45,6 +45,18 @@ ..\..\packages\Microsoft.Web.Xdt.2.1.1\lib\net40\Microsoft.Web.XmlTransform.dll + + ..\..\packages\Mono.Cecil.0.9.5.4\lib\net40\Mono.Cecil.dll + + + ..\..\packages\Mono.Cecil.0.9.5.4\lib\net40\Mono.Cecil.Mdb.dll + + + ..\..\packages\Mono.Cecil.0.9.5.4\lib\net40\Mono.Cecil.Pdb.dll + + + ..\..\packages\Mono.Cecil.0.9.5.4\lib\net40\Mono.Cecil.Rocks.dll + False ..\..\packages\NuGet.Core.2.8.2\lib\net40-Client\NuGet.Core.dll diff --git a/src/Squirrel/SquirrelAwareExecutableDetector.cs b/src/Squirrel/SquirrelAwareExecutableDetector.cs index f0ce48e1..c26a3723 100644 --- a/src/Squirrel/SquirrelAwareExecutableDetector.cs +++ b/src/Squirrel/SquirrelAwareExecutableDetector.cs @@ -7,6 +7,7 @@ using System.Reflection; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; +using Mono.Cecil; namespace Squirrel { @@ -35,11 +36,13 @@ namespace Squirrel { try { - var assembly = Assembly.ReflectionOnlyLoadFrom(executable); - var attrs = assembly.GetCustomAttributesData(); + var assembly = AssemblyDefinition.ReadAssembly(executable); + if (!assembly.HasCustomAttributes) return null; + + var attrs = assembly.CustomAttributes; var attribute = attrs.FirstOrDefault(x => { - if (x.AttributeType != typeof(AssemblyMetadataAttribute)) return false; + if (x.AttributeType.FullName != typeof(AssemblyMetadataAttribute).FullName) return false; if (x.ConstructorArguments.Count != 2) return false; return x.ConstructorArguments[0].Value.ToString() == "SquirrelAwareVersion"; }); diff --git a/src/Squirrel/packages.config b/src/Squirrel/packages.config index 7fcdc7d0..eea75f21 100644 --- a/src/Squirrel/packages.config +++ b/src/Squirrel/packages.config @@ -1,6 +1,7 @@  + \ No newline at end of file From 2c3fddb30cbfe503dd18bf50842bd0b969eea781 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Thu, 21 Aug 2014 14:18:07 -0700 Subject: [PATCH 15/19] Test update --- test/ApplyReleasesTests.cs | 40 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/test/ApplyReleasesTests.cs b/test/ApplyReleasesTests.cs index 3e7e2f1f..c145df50 100644 --- a/test/ApplyReleasesTests.cs +++ b/test/ApplyReleasesTests.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; +using System.Text; using System.Threading; using System.Threading.Tasks; using NuGet; @@ -50,10 +51,49 @@ namespace Squirrel.Tests Assert.False(File.Exists(Path.Combine(tempDir, "theApp", "app-0.1.0", "args2.txt"))); Assert.True(File.Exists(Path.Combine(tempDir, "theApp", "app-0.1.0", "args.txt"))); + + var text = File.ReadAllText(Path.Combine(tempDir, "theApp", "app-0.1.0", "args.txt"), Encoding.UTF8); + Assert.Contains("firstrun", text); } } } + [Fact] + public async Task UpgradeRunsSquirrelAwareAppsWithUpgradeFlag() + { + string tempDir; + string remotePkgDir; + + using (Utility.WithTempDirectory(out tempDir)) + using (Utility.WithTempDirectory(out remotePkgDir)) { + IntegrationTestHelper.CreateFakeInstalledApp("0.1.0", remotePkgDir); + var pkgs = ReleaseEntry.BuildReleasesFile(remotePkgDir); + ReleaseEntry.WriteReleaseFile(pkgs, Path.Combine(remotePkgDir, "RELEASES")); + + using (var fixture = new UpdateManager(remotePkgDir, "theApp", FrameworkVersion.Net45, tempDir)) { + await fixture.FullInstall(); + } + + await Task.Delay(1000); + + IntegrationTestHelper.CreateFakeInstalledApp("0.2.0", remotePkgDir); + pkgs = ReleaseEntry.BuildReleasesFile(remotePkgDir); + ReleaseEntry.WriteReleaseFile(pkgs, Path.Combine(remotePkgDir, "RELEASES")); + + using (var fixture = new UpdateManager(remotePkgDir, "theApp", FrameworkVersion.Net45, tempDir)) { + await fixture.UpdateApp(); + } + + await Task.Delay(1000); + + Assert.False(File.Exists(Path.Combine(tempDir, "theApp", "app-0.2.0", "args2.txt"))); + Assert.True(File.Exists(Path.Combine(tempDir, "theApp", "app-0.2.0", "args.txt"))); + + var text = File.ReadAllText(Path.Combine(tempDir, "theApp", "app-0.2.0", "args.txt"), Encoding.UTF8); + Assert.Contains("updated|0.2.0", text); + } + } + [Fact] public void WhenNoNewReleasesAreAvailableTheListIsEmpty() { From c53e9ea49dd2e8a6a4d21ef1fb79f37f46c46c46 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Thu, 21 Aug 2014 14:21:51 -0700 Subject: [PATCH 16/19] Verify full uninstall works --- test/ApplyReleasesTests.cs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/test/ApplyReleasesTests.cs b/test/ApplyReleasesTests.cs index c145df50..0ee199a6 100644 --- a/test/ApplyReleasesTests.cs +++ b/test/ApplyReleasesTests.cs @@ -94,6 +94,44 @@ namespace Squirrel.Tests } } + [Fact] + public async Task FullUninstallRemovesAllVersions() + { + string tempDir; + string remotePkgDir; + + using (Utility.WithTempDirectory(out tempDir)) + using (Utility.WithTempDirectory(out remotePkgDir)) { + IntegrationTestHelper.CreateFakeInstalledApp("0.1.0", remotePkgDir); + var pkgs = ReleaseEntry.BuildReleasesFile(remotePkgDir); + ReleaseEntry.WriteReleaseFile(pkgs, Path.Combine(remotePkgDir, "RELEASES")); + + using (var fixture = new UpdateManager(remotePkgDir, "theApp", FrameworkVersion.Net45, tempDir)) { + await fixture.FullInstall(); + } + + await Task.Delay(1000); + + IntegrationTestHelper.CreateFakeInstalledApp("0.2.0", remotePkgDir); + pkgs = ReleaseEntry.BuildReleasesFile(remotePkgDir); + ReleaseEntry.WriteReleaseFile(pkgs, Path.Combine(remotePkgDir, "RELEASES")); + + using (var fixture = new UpdateManager(remotePkgDir, "theApp", FrameworkVersion.Net45, tempDir)) { + await fixture.UpdateApp(); + } + + await Task.Delay(1000); + + using (var fixture = new UpdateManager(remotePkgDir, "theApp", FrameworkVersion.Net45, tempDir)) { + await fixture.FullUninstall(); + } + + Assert.False(File.Exists(Path.Combine(tempDir, "theApp", "app-0.1.0", "args.txt"))); + Assert.False(File.Exists(Path.Combine(tempDir, "theApp", "app-0.2.0", "args.txt"))); + Assert.False(Directory.Exists(Path.Combine(tempDir, "theApp"))); + } + } + [Fact] public void WhenNoNewReleasesAreAvailableTheListIsEmpty() { From 330715abe0b9b4f66f4f8d5324022c00c0dbc897 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Thu, 21 Aug 2014 14:24:49 -0700 Subject: [PATCH 17/19] Delete some tests that are now covered by other tests --- test/CheckForUpdateTests.cs | 68 ----------------------------------- test/DownloadReleasesTests.cs | 30 ---------------- 2 files changed, 98 deletions(-) diff --git a/test/CheckForUpdateTests.cs b/test/CheckForUpdateTests.cs index 340c9af9..adce8174 100644 --- a/test/CheckForUpdateTests.cs +++ b/test/CheckForUpdateTests.cs @@ -41,74 +41,6 @@ namespace Squirrel.Tests */ } - [Fact] - public void NoLocalReleasesFileMeansWeStartFromScratch() - { - Assert.False(true, "Rewrite this to be an integration test"); - /* - string localPackagesDir = Path.Combine(".", "theApp", "packages"); - string localReleasesFile = Path.Combine(localPackagesDir, "RELEASES"); - - var fileInfo = new Mock(); - fileInfo.Setup(x => x.Exists).Returns(false); - - var dirInfo = new Mock(); - dirInfo.Setup(x => x.Exists).Returns(true); - - var fs = new Mock(); - fs.Setup(x => x.GetFileInfo(localReleasesFile)).Returns(fileInfo.Object); - fs.Setup(x => x.CreateDirectoryRecursive(localPackagesDir)).Verifiable(); - fs.Setup(x => x.DeleteDirectoryRecursive(localPackagesDir)).Verifiable(); - fs.Setup(x => x.GetDirectoryInfo(localPackagesDir)).Returns(dirInfo.Object); - - var urlDownloader = new Mock(); - var dlPath = IntegrationTestHelper.GetPath("fixtures", "RELEASES-OnePointOne"); - urlDownloader.Setup(x => x.DownloadUrl(It.IsAny(), It.IsAny>())) - .Returns(Observable.Return(File.ReadAllText(dlPath, Encoding.UTF8))); - - var fixture = new UpdateManager("http://lol", "theApp", FrameworkVersion.Net40, ".", fs.Object, urlDownloader.Object); - using (fixture) { - fixture.CheckForUpdate().First(); - } - - fs.Verify(x => x.CreateDirectoryRecursive(localPackagesDir), Times.Once()); - fs.Verify(x => x.DeleteDirectoryRecursive(localPackagesDir), Times.Once()); - */ - } - - [Fact] - public void NoLocalDirectoryMeansWeStartFromScratch() - { - Assert.False(true, "Rewrite this to be an integration test"); - /* - string localPackagesDir = Path.Combine(".", "theApp", "packages"); - string localReleasesFile = Path.Combine(localPackagesDir, "RELEASES"); - - var fileInfo = new Mock(); - fileInfo.Setup(x => x.Exists).Returns(false); - - var dirInfo = new Mock(); - dirInfo.Setup(x => x.Exists).Returns(false); - - var fs = new Mock(); - fs.Setup(x => x.GetFileInfo(localReleasesFile)).Returns(fileInfo.Object); - fs.Setup(x => x.CreateDirectoryRecursive(It.IsAny())).Verifiable(); - fs.Setup(x => x.GetDirectoryInfo(localPackagesDir)).Returns(dirInfo.Object); - - var urlDownloader = new Mock(); - var dlPath = IntegrationTestHelper.GetPath("fixtures", "RELEASES-OnePointOne"); - urlDownloader.Setup(x => x.DownloadUrl(It.IsAny(), It.IsAny>())) - .Returns(Observable.Return(File.ReadAllText(dlPath, Encoding.UTF8))); - - var fixture = new UpdateManager("http://lol", "theApp", FrameworkVersion.Net40, ".", fs.Object, urlDownloader.Object); - using (fixture) { - fixture.CheckForUpdate().First(); - } - - fs.Verify(x => x.CreateDirectoryRecursive(localPackagesDir), Times.Once()); - */ - } - [Fact] public void CorruptedReleaseFileMeansWeStartFromScratch() { diff --git a/test/DownloadReleasesTests.cs b/test/DownloadReleasesTests.cs index dc871678..07228b74 100644 --- a/test/DownloadReleasesTests.cs +++ b/test/DownloadReleasesTests.cs @@ -13,36 +13,6 @@ namespace Squirrel.Tests { public class DownloadReleasesTests : IEnableLogger { - [Fact] - public void ChecksumShouldPassOnValidPackages() - { - Assert.False(true, "Rewrite this to be an integration test"); - - /* - var filename = "Squirrel.Core.1.0.0.0.nupkg"; - var nuGetPkg = IntegrationTestHelper.GetPath("fixtures", filename); - var fs = new Mock(); - var urlDownloader = new Mock(); - - ReleaseEntry entry; - using (var f = File.OpenRead(nuGetPkg)) { - entry = ReleaseEntry.GenerateFromFile(f, filename); - } - - var fileInfo = new Mock(); - fileInfo.Setup(x => x.OpenRead()).Returns(File.OpenRead(nuGetPkg)); - fileInfo.Setup(x => x.Exists).Returns(true); - fileInfo.Setup(x => x.Length).Returns(new FileInfo(nuGetPkg).Length); - - fs.Setup(x => x.GetFileInfo(Path.Combine(".", "theApp", "packages", filename))).Returns(fileInfo.Object); - - var fixture = ExposedObject.From( - new UpdateManager("http://lol", "theApp", FrameworkVersion.Net40, ".", fs.Object, urlDownloader.Object)); - - fixture.checksumPackage(entry); - */ - } - [Fact] public void ChecksumShouldFailIfFilesAreMissing() { From 7dc244d6446ec5dd01b578e24fa0a2682a89fa7a Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Thu, 21 Aug 2014 14:47:22 -0700 Subject: [PATCH 18/19] Annotate return types --- src/Squirrel/NativeMethods.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Squirrel/NativeMethods.cs b/src/Squirrel/NativeMethods.cs index 611f4d3b..a717002c 100644 --- a/src/Squirrel/NativeMethods.cs +++ b/src/Squirrel/NativeMethods.cs @@ -10,7 +10,7 @@ namespace Squirrel static class NativeMethods { [DllImport("version.dll", SetLastError = true)] - public static extern bool GetFileVersionInfo( + [return:MarshalAs(UnmanagedType.Bool)] public static extern bool GetFileVersionInfo( string lpszFileName, IntPtr dwHandleIgnored, int dwLen, @@ -22,6 +22,10 @@ namespace Squirrel IntPtr dwHandleIgnored); [DllImport("version.dll")] - public static extern bool VerQueryValue(byte[] pBlock, string pSubBlock, out IntPtr pValue, out int len); + [return:MarshalAs(UnmanagedType.Bool)] public static extern bool VerQueryValue( + byte[] pBlock, + string pSubBlock, + out IntPtr pValue, + out int len); } } From 144ca1b7cdc2754ca7b7765274ad8af7b72f772d Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Thu, 21 Aug 2014 14:51:25 -0700 Subject: [PATCH 19/19] Explain the magic One --- src/Squirrel/UpdateManager.ApplyReleases.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Squirrel/UpdateManager.ApplyReleases.cs b/src/Squirrel/UpdateManager.ApplyReleases.cs index 6125ab16..cffec376 100644 --- a/src/Squirrel/UpdateManager.ApplyReleases.cs +++ b/src/Squirrel/UpdateManager.ApplyReleases.cs @@ -197,7 +197,7 @@ namespace Squirrel var squirrelApps = SquirrelAwareExecutableDetector.GetAllSquirrelAwareApps(targetDir.FullName); // For each app, run the install command in-order and wait - await squirrelApps.ForEachAsync(exe => Utility.InvokeProcessAsync(exe, args), 1); + await squirrelApps.ForEachAsync(exe => Utility.InvokeProcessAsync(exe, args), 1 /* at a time */); if (!isInitialInstall) return;