diff --git a/src/managed/Microsoft.NET.HostModel/AppHost/AppHostCustomizationUnsupportedOSException.cs b/src/managed/Microsoft.NET.HostModel/AppHost/AppHostCustomizationUnsupportedOSException.cs new file mode 100644 index 0000000000..ce76a6de4b --- /dev/null +++ b/src/managed/Microsoft.NET.HostModel/AppHost/AppHostCustomizationUnsupportedOSException.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace Microsoft.NET.HostModel.AppHost +{ + /// + /// The application host executable cannot be customized because adding resources requires + /// that the build be performed on Windows (excluding Nano Server). + /// + public class AppHostCustomizationUnsupportedOSException : AppHostUpdateException + { + } +} + diff --git a/src/managed/Microsoft.NET.HostModel/AppHost/AppHostNotCUIException.cs b/src/managed/Microsoft.NET.HostModel/AppHost/AppHostNotCUIException.cs new file mode 100644 index 0000000000..a119a60b56 --- /dev/null +++ b/src/managed/Microsoft.NET.HostModel/AppHost/AppHostNotCUIException.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace Microsoft.NET.HostModel.AppHost +{ + /// + /// Unable to use the input file as application host executable because it's not a + /// Windows executable for the CUI (Console) subsystem. + /// + public class AppHostNotCUIException : AppHostUpdateException + { + } +} + diff --git a/src/managed/Microsoft.NET.HostModel/AppHost/AppHostNotPEFileException.cs b/src/managed/Microsoft.NET.HostModel/AppHost/AppHostNotPEFileException.cs new file mode 100644 index 0000000000..c788e51826 --- /dev/null +++ b/src/managed/Microsoft.NET.HostModel/AppHost/AppHostNotPEFileException.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace Microsoft.NET.HostModel.AppHost +{ + /// + /// Unable to use the input file as an application host executable + /// because it's not a Windows PE file + /// + public class AppHostNotPEFileException : AppHostUpdateException + { + } +} + diff --git a/src/managed/Microsoft.NET.HostModel/AppHost/AppHostUpdateException.cs b/src/managed/Microsoft.NET.HostModel/AppHost/AppHostUpdateException.cs new file mode 100644 index 0000000000..befd9131ac --- /dev/null +++ b/src/managed/Microsoft.NET.HostModel/AppHost/AppHostUpdateException.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace Microsoft.NET.HostModel.AppHost +{ + /// + /// An instance of this exception is thrown when an AppHost binary update + /// fails due to known user errors. + /// + public class AppHostUpdateException : Exception + { + } +} + diff --git a/src/managed/Microsoft.NET.HostModel/AppHost/BinaryUpdateException.cs b/src/managed/Microsoft.NET.HostModel/AppHost/AppNameTooLongException.cs similarity index 59% rename from src/managed/Microsoft.NET.HostModel/AppHost/BinaryUpdateException.cs rename to src/managed/Microsoft.NET.HostModel/AppHost/AppNameTooLongException.cs index ecab882d27..cde719e960 100644 --- a/src/managed/Microsoft.NET.HostModel/AppHost/BinaryUpdateException.cs +++ b/src/managed/Microsoft.NET.HostModel/AppHost/AppNameTooLongException.cs @@ -7,13 +7,14 @@ namespace Microsoft.NET.HostModel.AppHost { /// - /// This exception is thrown when an AppHost binary update fails due to known user errors. + /// Given app file name is longer than 1024 bytes /// - public class BinaryUpdateException : Exception + public class AppNameTooLongException : AppHostUpdateException { - public BinaryUpdateException(string message) : - base(message) + public string LongName; + public AppNameTooLongException(string name) { + LongName = name; } } } diff --git a/src/managed/Microsoft.NET.HostModel/AppHost/AppUpdatePlaceHolderNotFoundException.cs b/src/managed/Microsoft.NET.HostModel/AppHost/AppUpdatePlaceHolderNotFoundException.cs new file mode 100644 index 0000000000..a497c2d673 --- /dev/null +++ b/src/managed/Microsoft.NET.HostModel/AppHost/AppUpdatePlaceHolderNotFoundException.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace Microsoft.NET.HostModel.AppHost +{ + /// + /// Unable to use input file as a valid application host executable, as it does not contain + /// the expected placeholder byte sequence. + /// + public class AppUpdatePlaceHolderNotFoundException : AppHostUpdateException + { + public byte[] MissingPattern; + + public AppUpdatePlaceHolderNotFoundException(byte[] pattern) + { + MissingPattern = pattern; + } + } +} + diff --git a/src/managed/Microsoft.NET.HostModel/AppHost/BinaryUtils.cs b/src/managed/Microsoft.NET.HostModel/AppHost/BinaryUtils.cs index 4e7cf100d4..415e73d71e 100644 --- a/src/managed/Microsoft.NET.HostModel/AppHost/BinaryUtils.cs +++ b/src/managed/Microsoft.NET.HostModel/AppHost/BinaryUtils.cs @@ -11,7 +11,7 @@ namespace Microsoft.NET.HostModel.AppHost { public static class BinaryUtils { - internal static unsafe bool SearchAndReplace( + internal static unsafe void SearchAndReplace( MemoryMappedViewAccessor accessor, byte[] searchPattern, byte[] patternToReplace) @@ -26,7 +26,7 @@ internal static unsafe bool SearchAndReplace( int position = KMPSearch(searchPattern, bytes, accessor.Capacity); if (position < 0) { - return false; + throw new AppUpdatePlaceHolderNotFoundException(searchPattern); } accessor.WriteArray( @@ -44,8 +44,6 @@ internal static unsafe bool SearchAndReplace( accessor.SafeMemoryMappedViewHandle.ReleasePointer(); } } - - return true; } private static unsafe void Pad0(byte[] searchPattern, byte[] patternToReplace, byte* bytes, int offset) @@ -59,31 +57,14 @@ private static unsafe void Pad0(byte[] searchPattern, byte[] patternToReplace, b } } - public static unsafe void SearchAndReplace(MemoryMappedFile mappedFile, byte[] searchPattern, byte[] patternToReplace) - { - using (var accessor = mappedFile.CreateViewAccessor()) - { - if (!SearchAndReplace(accessor, searchPattern, patternToReplace)) - { - throw new BinaryUpdateException($"SearchPattern {searchPattern} not found."); - } - } - } - public static unsafe void SearchAndReplace(string filePath, byte[] searchPattern, byte[] patternToReplace) { using (var mappedFile = MemoryMappedFile.CreateFromFile(filePath)) { - SearchAndReplace(mappedFile, searchPattern, patternToReplace); - } - } - - public static unsafe int SearchInFile(MemoryMappedFile mappedFile, byte[] searchPattern) - { - using (var accessor = mappedFile.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read)) - { - var safeBuffer = accessor.SafeMemoryMappedViewHandle; - return KMPSearch(searchPattern, (byte*)safeBuffer.DangerousGetHandle(), (int)safeBuffer.ByteLength); + using (var accessor = mappedFile.CreateViewAccessor()) + { + SearchAndReplace(accessor, searchPattern, patternToReplace); + } } } @@ -91,7 +72,11 @@ public static unsafe int SearchInFile(string filePath, byte[] searchPattern) { using (var mappedFile = MemoryMappedFile.CreateFromFile(filePath)) { - return SearchInFile(mappedFile, searchPattern); + using (var accessor = mappedFile.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read)) + { + var safeBuffer = accessor.SafeMemoryMappedViewHandle; + return KMPSearch(searchPattern, (byte*)safeBuffer.DangerousGetHandle(), (int)safeBuffer.ByteLength); + } } } @@ -226,10 +211,7 @@ internal static unsafe bool IsPEImage(MemoryMappedViewAccessor accessor) /// This method will attempt to set the subsystem to GUI. The apphost file should be a windows PE file. /// /// The memory accessor which has the apphost file opened. - /// The path to the source apphost. - internal static unsafe void SetWindowsGraphicalUserInterfaceBit( - MemoryMappedViewAccessor accessor, - string appHostSourcePath) + internal static unsafe void SetWindowsGraphicalUserInterfaceBit(MemoryMappedViewAccessor accessor) { byte* pointer = null; @@ -243,7 +225,7 @@ internal static unsafe void SetWindowsGraphicalUserInterfaceBit( if (accessor.Capacity < peHeaderOffset + SubsystemOffset + sizeof(UInt16)) { - throw new BinaryUpdateException($" Unable to use '{appHostSourcePath}' as application host executable because it's not a Windows PE file"); + throw new AppHostNotPEFileException(); } UInt16* subsystem = ((UInt16*)(bytes + peHeaderOffset + SubsystemOffset)); @@ -252,7 +234,7 @@ internal static unsafe void SetWindowsGraphicalUserInterfaceBit( // The subsystem of the prebuilt apphost should be set to CUI if (subsystem[0] != WindowsCUISubsystem) { - throw new BinaryUpdateException("Unable to use '{appHostSourcePath}' as application host executable because it's not a Windows executable for the CUI (Console) subsystem"); + throw new AppHostNotCUIException(); } // Set the subsystem to GUI @@ -266,5 +248,61 @@ internal static unsafe void SetWindowsGraphicalUserInterfaceBit( } } } + + public static unsafe void SetWindowsGraphicalUserInterfaceBit(string filePath) + { + using (var mappedFile = MemoryMappedFile.CreateFromFile(filePath)) + { + using (var accessor = mappedFile.CreateViewAccessor()) + { + SetWindowsGraphicalUserInterfaceBit(accessor); + } + } + } + + /// + /// This method will return the subsystem CUI/GUI value. The apphost file should be a windows PE file. + /// + /// The memory accessor which has the apphost file opened. + internal static unsafe UInt16 GetWindowsGraphicalUserInterfaceBit(MemoryMappedViewAccessor accessor) + { + byte* pointer = null; + + try + { + accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref pointer); + byte* bytes = pointer + accessor.PointerOffset; + + // https://en.wikipedia.org/wiki/Portable_Executable + UInt32 peHeaderOffset = ((UInt32*)(bytes + PEHeaderPointerOffset))[0]; + + if (accessor.Capacity < peHeaderOffset + SubsystemOffset + sizeof(UInt16)) + { + throw new AppHostNotPEFileException(); + } + + UInt16* subsystem = ((UInt16*)(bytes + peHeaderOffset + SubsystemOffset)); + + return subsystem[0]; + } + finally + { + if (pointer != null) + { + accessor.SafeMemoryMappedViewHandle.ReleasePointer(); + } + } + } + + public static unsafe UInt16 GetWindowsGraphicalUserInterfaceBit(string filePath) + { + using (var mappedFile = MemoryMappedFile.CreateFromFile(filePath)) + { + using (var accessor = mappedFile.CreateViewAccessor()) + { + return GetWindowsGraphicalUserInterfaceBit(accessor); + } + } + } } } diff --git a/src/managed/Microsoft.NET.HostModel/AppHost/AppUpdater.cs b/src/managed/Microsoft.NET.HostModel/AppHost/HostWriter.cs similarity index 63% rename from src/managed/Microsoft.NET.HostModel/AppHost/AppUpdater.cs rename to src/managed/Microsoft.NET.HostModel/AppHost/HostWriter.cs index a8689ba0a0..ff5ec92073 100644 --- a/src/managed/Microsoft.NET.HostModel/AppHost/AppUpdater.cs +++ b/src/managed/Microsoft.NET.HostModel/AppHost/HostWriter.cs @@ -12,7 +12,7 @@ namespace Microsoft.NET.HostModel.AppHost /// Embeds the App Name into the AppHost.exe /// If an apphost is a single-file bundle, updates the location of the bundle headers. /// - public static class AppUpdater + public static class HostWriter { /// /// hash value embedded in default apphost executable in a place where the path to the app binary should be stored. @@ -20,7 +20,7 @@ public static class AppUpdater private const string AppBinaryPathPlaceholder = "c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2"; private readonly static byte[] AppBinaryPathPlaceholderSearchValue = Encoding.UTF8.GetBytes(AppBinaryPathPlaceholder); - private const string BundleHeaderPlaceholder = "c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f3"; + private const string BundleHeaderPlaceholder = "db2a6C16fec7fbebe3539d534a3471e95ea6e85c718cba293996a8ac85F90427"; private readonly static byte[] BundleHeaderPlaceholderSearchValue = Encoding.UTF8.GetBytes(BundleHeaderPlaceholder); /// @@ -30,28 +30,21 @@ public static class AppUpdater /// The destination path for desired location to place, including the file name /// Full path to app binary or relative path to the result apphost file /// Specify whether to set the subsystem to GUI. Only valid for PE apphosts. - /// Path to the intermediate assembly, used for copying resources to PE apphosts. - public static void UpdateAppPath( + /// Path to the intermediate assembly, used for copying resources to PE apphosts. + public static void CreateAppHost( string appHostSourceFilePath, string appHostDestinationFilePath, string appBinaryFilePath, bool windowsGraphicalUserInterface = false, - string intermediateAssembly = null) + string assemblyToCopyResorcesFrom = null) { var bytesToWrite = Encoding.UTF8.GetBytes(appBinaryFilePath); if (bytesToWrite.Length > 1024) { - throw new BinaryUpdateException($"Given file name {appBinaryFilePath} is longer than 1024 bytes"); + throw new AppNameTooLongException(appBinaryFilePath); } - var destinationDirectory = new FileInfo(appHostDestinationFilePath).Directory.FullName; - if (!Directory.Exists(destinationDirectory)) - { - Directory.CreateDirectory(destinationDirectory); - } - - // Copy apphost to destination path so it inherits the same attributes/permissions. - File.Copy(appHostSourceFilePath, appHostDestinationFilePath, overwrite: true); + CopyAppHost(appHostSourceFilePath, appHostDestinationFilePath); // Re-write the destination apphost with the proper contents. bool appHostIsPEImage = false; @@ -59,10 +52,7 @@ public static void UpdateAppPath( { using (MemoryMappedViewAccessor accessor = memoryMappedFile.CreateViewAccessor()) { - if(!BinaryUtils.SearchAndReplace(accessor, AppBinaryPathPlaceholderSearchValue, bytesToWrite)) - { - throw new BinaryUpdateException($"Unable to use '{appHostSourceFilePath}' as application host executable as it does not contain the expected placeholder byte sequence '{AppBinaryPathPlaceholder}' that would mark where the application name would be written"); - } + BinaryUtils.SearchAndReplace(accessor, AppBinaryPathPlaceholderSearchValue, bytesToWrite); appHostIsPEImage = BinaryUtils.IsPEImage(accessor); @@ -70,23 +60,27 @@ public static void UpdateAppPath( { if (!appHostIsPEImage) { - throw new BinaryUpdateException($"Unable to use '{appHostSourceFilePath}' as application host executable because it's not a Windows executable for the CUI (Console) subsystem"); + throw new AppHostNotPEFileException(); } - BinaryUtils.SetWindowsGraphicalUserInterfaceBit(accessor, appHostSourceFilePath); + BinaryUtils.SetWindowsGraphicalUserInterfaceBit(accessor); } } } - if (intermediateAssembly != null && appHostIsPEImage) + if (assemblyToCopyResorcesFrom != null && appHostIsPEImage) { if (ResourceUpdater.IsSupportedOS()) { // Copy resources from managed dll to the apphost new ResourceUpdater(appHostDestinationFilePath) - .AddResourcesFromPEImage(intermediateAssembly) + .AddResourcesFromPEImage(assemblyToCopyResorcesFrom) .Update(); } + else + { + throw new AppHostCustomizationUnsupportedOSException(); + } } // Memory-mapped write does not updating last write time @@ -94,15 +88,28 @@ public static void UpdateAppPath( } /// - /// Create an AppHost with embedded configuration of app binary location + /// Create an AppHost configured to be a single-file bundle. /// /// The path of Apphost template, which has the place holder /// The destination path for desired location to place, including the file name /// The offset to the location of bundle header - public static void UpdateBundleHeader( + public static void CreateBundle( string appHostSourceFilePath, string appHostDestinationFilePath, long bundleHeaderOffset) + { + CopyAppHost(appHostSourceFilePath, appHostDestinationFilePath); + + // Re-write the destination apphost with the proper contents. + BinaryUtils.SearchAndReplace(appHostDestinationFilePath, BundleHeaderPlaceholderSearchValue, BitConverter.GetBytes(bundleHeaderOffset)); + + // Memory-mapped write does not updating last write time + File.SetLastWriteTimeUtc(appHostDestinationFilePath, DateTime.UtcNow); + } + + private static void CopyAppHost( + string appHostSourceFilePath, + string appHostDestinationFilePath) { var destinationDirectory = new FileInfo(appHostDestinationFilePath).Directory.FullName; if (!Directory.Exists(destinationDirectory)) @@ -112,22 +119,6 @@ public static void UpdateBundleHeader( // Copy apphost to destination path so it inherits the same attributes/permissions. File.Copy(appHostSourceFilePath, appHostDestinationFilePath, overwrite: true); - - // Re-write the destination apphost with the proper contents. - using (var memoryMappedFile = MemoryMappedFile.CreateFromFile(appHostDestinationFilePath)) - { - using (MemoryMappedViewAccessor accessor = memoryMappedFile.CreateViewAccessor()) - { - if (!BinaryUtils.SearchAndReplace(accessor, BundleHeaderPlaceholderSearchValue, BitConverter.GetBytes(bundleHeaderOffset))) - { - throw new BinaryUpdateException($"Unable to use '{appHostSourceFilePath}' as application host executable as it does not contain the expected placeholder byte sequence '{BundleHeaderPlaceholder}' that would mark where the bundle header offset would be written"); - } - } - } - - // Memory-mapped write does not updating last write time - File.SetLastWriteTimeUtc(appHostDestinationFilePath, DateTime.UtcNow); } - } }