1 // Copyright (c) Microsoft. All rights reserved. 2 // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 4 using System; 5 using System.Collections; 6 using System.Globalization; 7 using System.IO; 8 using System.Resources; 9 using System.Reflection; 10 using System.Diagnostics; 11 using System.Runtime.InteropServices; 12 using System.Threading; 13 14 namespace Microsoft.Build.Tasks.Deployment.Bootstrapper 15 { 16 internal class ResourceUpdater 17 { 18 private const int ERROR_SHARING_VIOLATION = -2147024864; 19 private ArrayList _stringResources; 20 private ArrayList _fileResources; 21 ResourceUpdater()22 public ResourceUpdater() 23 { 24 _stringResources = new ArrayList(); 25 _fileResources = new ArrayList(); 26 } 27 AddStringResource(int type, string name, string data)28 public void AddStringResource(int type, string name, string data) 29 { 30 _stringResources.Add(new StringResource(type, name, data)); 31 } 32 AddFileResource(string filename, string key)33 public void AddFileResource(string filename, string key) 34 { 35 _fileResources.Add(new FileResource(filename, key)); 36 } 37 UpdateResources(string filename, BuildResults results)38 public bool UpdateResources(string filename, BuildResults results) 39 { 40 bool returnValue = true; 41 int beginUpdateRetries = 20; // Number of retries 42 const int beginUpdateRetryInterval = 100; // In milliseconds 43 bool endUpdate = false; // Only call EndUpdateResource() if this is true 44 45 // Directory.GetCurrentDirectory() has previously been set to the project location 46 string filePath = System.IO.Path.Combine(Directory.GetCurrentDirectory(), filename); 47 48 if (_stringResources.Count == 0 && _fileResources.Count == 0) 49 return true; 50 IntPtr hUpdate = IntPtr.Zero; 51 52 try 53 { 54 hUpdate = NativeMethods.BeginUpdateResourceW(filePath, false); 55 while (IntPtr.Zero == hUpdate && Marshal.GetHRForLastWin32Error() == ResourceUpdater.ERROR_SHARING_VIOLATION && beginUpdateRetries > 0) // If it equals 0x80070020 (ERROR_SHARING_VIOLATION), sleep & retry 56 { 57 // This warning can be useful for debugging, but shouldn't be displayed to an actual user 58 // results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Warning, "GenerateBootstrapper.General", String.Format("Unable to begin updating resource for {0} with error {1:X}, trying again after short sleep", filename, Marshal.GetHRForLastWin32Error()))); 59 hUpdate = NativeMethods.BeginUpdateResourceW(filePath, false); 60 beginUpdateRetries--; 61 Thread.Sleep(beginUpdateRetryInterval); 62 } 63 // If after all that we still failed, throw a build error 64 if (IntPtr.Zero == hUpdate) 65 { 66 results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.General", String.Format("Unable to begin updating resource for {0} with error {1:X}", filename, Marshal.GetHRForLastWin32Error()))); 67 return false; 68 } 69 70 endUpdate = true; 71 72 if (hUpdate != IntPtr.Zero) 73 { 74 foreach (StringResource resource in _stringResources) 75 { 76 byte[] data = StringToByteArray(resource.Data); 77 78 if (!NativeMethods.UpdateResourceW(hUpdate, (IntPtr)resource.Type, resource.Name, 0, data, data.Length)) 79 { 80 results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.General", String.Format("Unable to update resource for {0} with error {1:X}", filename, Marshal.GetHRForLastWin32Error()))); 81 return false; 82 } 83 } 84 85 if (_fileResources.Count > 0) 86 { 87 int index = 0; 88 byte[] countArray = StringToByteArray(_fileResources.Count.ToString("G", CultureInfo.InvariantCulture)); 89 if (!NativeMethods.UpdateResourceW(hUpdate, (IntPtr)42, "COUNT", 0, countArray, countArray.Length)) 90 { 91 results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.General", String.Format("Unable to update count resource for {0} with error {1:X}", filename, Marshal.GetHRForLastWin32Error()))); 92 return false; 93 } 94 95 foreach (FileResource resource in _fileResources) 96 { 97 // Read in the file data 98 int fileLength = 0; 99 byte[] fileContent = null; 100 using (FileStream fs = System.IO.File.OpenRead(resource.Filename)) 101 { 102 fileLength = (int)fs.Length; 103 fileContent = new byte[fileLength]; 104 105 fs.Read(fileContent, 0, fileLength); 106 } 107 108 // Update the resources to include this file's data 109 string dataName = string.Format(CultureInfo.InvariantCulture, "FILEDATA{0}", index); 110 111 if (!NativeMethods.UpdateResourceW(hUpdate, (IntPtr)42, dataName, 0, fileContent, fileLength)) 112 { 113 results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.General", String.Format("Unable to update data resource for {0} with error {1:X}", filename, Marshal.GetHRForLastWin32Error()))); 114 return false; 115 } 116 117 // Add this file's key to the resources 118 string keyName = string.Format(CultureInfo.InvariantCulture, "FILEKEY{0}", index); 119 byte[] data = StringToByteArray(resource.Key); 120 if (!NativeMethods.UpdateResourceW(hUpdate, (IntPtr)42, keyName, 0, data, data.Length)) 121 { 122 results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.General", String.Format("Unable to update key resource for {0} with error {1:X}", filename, Marshal.GetHRForLastWin32Error()))); 123 return false; 124 } 125 126 index++; 127 } 128 } 129 } 130 } 131 finally 132 { 133 if (endUpdate && !NativeMethods.EndUpdateResource(hUpdate, false)) 134 { 135 results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.General", String.Format("Unable to finish updating resource for {0} with error {1:X}", filename, Marshal.GetHRForLastWin32Error()))); 136 returnValue = false; 137 } 138 } 139 140 return returnValue; 141 } 142 StringToByteArray(string str)143 private byte[] StringToByteArray(string str) 144 { 145 byte[] strBytes = System.Text.Encoding.Unicode.GetBytes(str); 146 byte[] data = new byte[strBytes.Length + 2]; 147 strBytes.CopyTo(data, 0); 148 data[data.Length - 2] = 0; 149 data[data.Length - 1] = 0; 150 return data; 151 } 152 153 private class StringResource 154 { 155 public int Type; 156 public string Name; 157 public string Data; 158 StringResource(int type, string name, string data)159 public StringResource(int type, string name, string data) 160 { 161 Type = type; 162 Name = name; 163 Data = data; 164 } 165 } 166 167 private class FileResource 168 { 169 public string Filename; 170 public string Key; 171 FileResource(string filename, string key)172 public FileResource(string filename, string key) 173 { 174 Filename = filename; 175 Key = key; 176 } 177 } 178 } 179 } 180