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