winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

WinGetLocalSource.cs (10633B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 
      4 namespace Microsoft.WinGetSourceCreator
      5 {
      6     using global::WinGetSourceCreator.Model;
      7     using System.Text.Json.Serialization;
      8     using System.Text.Json;
      9     using Microsoft.WinGetUtil.Api;
     10     using Microsoft.WinGetUtil.Interfaces;
     11 
     12     public class WinGetLocalSource
     13     {
     14         private readonly string workingDirectory;
     15         private readonly ManifestTokens tokens;
     16         private readonly Signature? signature;
     17 
     18         public static void CreateFromLocalSourceFile(string localSourceFile)
     19         {
     20             var content = File.ReadAllText(localSourceFile);
     21             content = Environment.ExpandEnvironmentVariables(content);
     22 
     23             var options = new JsonSerializerOptions
     24             {
     25                 PropertyNameCaseInsensitive = true,
     26                 Converters =
     27                 {
     28                     new JsonStringEnumConverter(JsonNamingPolicy.CamelCase)
     29                 }
     30             };
     31 
     32             content = content.Replace("\\", "/");
     33 
     34             var localSource = JsonSerializer.Deserialize<LocalSource>(content, options);
     35             if (localSource == null)
     36             {
     37                 throw new Exception("Failed deserializing");
     38             }
     39 
     40             CreateLocalSource(localSource);
     41         }
     42 
     43         public static void CreateLocalSource(LocalSource localSource)
     44         {
     45             localSource.Validate();
     46 
     47             var wingetSource = new WinGetLocalSource(localSource.WorkingDirectory, localSource.Signature);
     48 
     49             if (localSource.LocalInstallers != null)
     50             {
     51                 foreach (var installer in localSource.LocalInstallers)
     52                 {
     53                     wingetSource.PrepareLocalInstaller(installer);
     54                 }
     55             }
     56 
     57             if (localSource.DynamicInstallers != null)
     58             {
     59                 foreach (var installer in localSource.DynamicInstallers)
     60                 {
     61                     wingetSource.PrepareDynamicInstaller(installer);
     62                 }
     63             }
     64 
     65             foreach (var localManifest in localSource.LocalManifests)
     66             {
     67                 wingetSource.PrepareManifest(localManifest);
     68             }
     69 
     70             var indexV2File = wingetSource.CreateIndex(localSource.GetIndexName(), 2, 0);
     71             _ = wingetSource.CreatePackage(localSource.GetSourceName(2), localSource.AppxManifest, indexV2File, localSource.Signature);
     72 
     73             var indexV1File = wingetSource.CreateIndex(localSource.GetIndexName());
     74             _ = wingetSource.CreatePackage(localSource.GetSourceName(1), localSource.AppxManifest, indexV1File, localSource.Signature);
     75         }
     76 
     77         public WinGetLocalSource(string workingDirectory, Signature? signature)
     78         {
     79             this.workingDirectory = Path.GetFullPath(workingDirectory);
     80 
     81             if (Directory.Exists(workingDirectory))
     82             {
     83                 Directory.Delete(workingDirectory, true);
     84             }
     85             Directory.CreateDirectory(workingDirectory);
     86 
     87             this.tokens = new();
     88             this.signature = signature;
     89         }
     90 
     91         public void PrepareDynamicInstaller(DynamicInstaller installer)
     92         {
     93             var sourceInstaller = new SourceInstaller(this.workingDirectory, installer);
     94             PrepareInstaller(sourceInstaller);
     95         }
     96 
     97         public void PrepareLocalInstaller(LocalInstaller installer)
     98         {
     99             var sourceInstaller = new SourceInstaller(this.workingDirectory, installer);
    100             PrepareInstaller(sourceInstaller);
    101         }
    102 
    103         public void PrepareManifest(string input)
    104         {
    105             if (File.Exists(input))
    106             {
    107 
    108                 CopyManifestFile(input, Path.Combine(this.workingDirectory, Path.GetFileName(input)));
    109             }
    110             else
    111             {
    112                 CopyManifestFiles(input, this.workingDirectory);
    113             }
    114         }
    115 
    116         public string CreateIndex(string indexName, uint? majorVersion = null, uint? minorVersion = null)
    117         {
    118             string fullPath = Path.Combine(this.workingDirectory, indexName);
    119 
    120             if (File.Exists(fullPath))
    121             {
    122                 File.Delete(fullPath);
    123             }
    124 
    125             WinGetFactory factory = new ();
    126             using IWinGetSQLiteIndex indexHelper = majorVersion == null ? factory.SQLiteIndexCreateLatestVersion(fullPath) : factory.SQLiteIndexCreate(fullPath, majorVersion.Value, minorVersion.GetValueOrDefault());
    127 
    128             Queue<string> filesQueue = new(Directory.EnumerateFiles(this.workingDirectory, "*.yaml", SearchOption.AllDirectories));
    129             while (filesQueue.Count > 0)
    130             {
    131                 int currentCount = filesQueue.Count;
    132 
    133                 for (int i = 0; i < currentCount; i++)
    134                 {
    135                     string file = filesQueue.Dequeue();
    136                     try
    137                     {
    138                         var rel = Path.GetRelativePath(this.workingDirectory, file);
    139                         indexHelper.AddManifest(file, rel);
    140                     }
    141                     catch
    142                     {
    143                         // If adding manifest to index fails, add to queue and try again.
    144                         // This can occur if there is a package dependency that has not yet been added to the index.
    145                         filesQueue.Enqueue(file);
    146                     }
    147                 }
    148 
    149                 if (filesQueue.Count == currentCount)
    150                 {
    151                     throw new InvalidOperationException("Failed to add all manifests in directory to index.");
    152                 }
    153             }
    154 
    155             indexHelper.PrepareForPackaging();
    156 
    157             return fullPath;
    158         }
    159 
    160         public string CreatePackage(string packageName, string inputAppxManifestFile, string indexPath, Signature? signature)
    161         {
    162             if (!File.Exists(inputAppxManifestFile))
    163             {
    164                 throw new FileNotFoundException(inputAppxManifestFile);
    165             }
    166 
    167             if (!File.Exists(indexPath))
    168             {
    169                 throw new FileNotFoundException(indexPath);
    170             }
    171 
    172             string appxManifestFile = Path.Combine(this.workingDirectory, "AppxManifest.xml");
    173             File.Copy(inputAppxManifestFile, appxManifestFile, true);
    174 
    175             if (signature != null && signature.Publisher != null)
    176             {
    177                 Helpers.ModifyAppxManifestIdentity(appxManifestFile, signature.Publisher);
    178             }
    179 
    180             string mappingFile = Path.Combine(this.workingDirectory, "MappingFile.txt");
    181 
    182             {
    183                 using StreamWriter outputFile = new(mappingFile, false);
    184                 outputFile.WriteLine("[Files]");
    185                 outputFile.WriteLine($"\"{indexPath}\" \"Public\\{Path.GetFileName(indexPath)}\"");
    186                 outputFile.WriteLine($"\"{appxManifestFile}\" \"AppxManifest.xml\"");
    187             }
    188 
    189             string outputPackage = Path.Combine(this.workingDirectory, packageName);
    190             Helpers.PackWithMappingFile(outputPackage, mappingFile);
    191 
    192             if (signature != null)
    193             {
    194                 Helpers.SignFile(outputPackage, signature);
    195             }
    196 
    197             return outputPackage;
    198         }
    199 
    200         // Copies all .yaml files
    201         private void CopyManifestFiles(string sourceDir, string destDir)
    202         {
    203             DirectoryInfo dir = new DirectoryInfo(sourceDir);
    204             DirectoryInfo[] dirs = dir.GetDirectories();
    205 
    206             FileInfo[] files = dir.GetFiles();
    207             foreach (FileInfo file in files)
    208             {
    209                 if (file.Extension == ".yaml")
    210                 {
    211                     CopyManifestFile(file.FullName, Path.Combine(destDir, file.Name));
    212                 }
    213             }
    214 
    215             foreach (DirectoryInfo subdir in dirs)
    216             {
    217                 CopyManifestFiles(subdir.FullName, Path.Combine(destDir, subdir.Name));
    218             }
    219         }
    220 
    221         // Copies a file and replaces any token found.
    222         private void CopyManifestFile(string sourceFile, string destinationFile)
    223         {
    224             if (!File.Exists(sourceFile))
    225             {
    226                 throw new FileNotFoundException(sourceFile);
    227             }
    228 
    229             var content = File.ReadAllText(sourceFile);
    230 
    231             foreach (var token in this.tokens.Tokens)
    232             {
    233                 if (content.Contains(token.Key))
    234                 {
    235                     content = content.Replace(token.Key, token.Value);
    236                 }
    237             }
    238 
    239             File.WriteAllText(destinationFile, content);
    240         }
    241 
    242         private void PrepareInstaller(SourceInstaller installer)
    243         {
    244             // Sign installer if needed.
    245             if (!installer.SkipSignature)
    246             {
    247                 var sig = this.GetSignature(installer);
    248                 if (sig != null)
    249                 {
    250                     Helpers.SignInstaller(installer, sig);
    251                 }
    252             }
    253 
    254             // Process hash token if needed.
    255             if (!string.IsNullOrEmpty(installer.HashToken))
    256             {
    257                 this.tokens.AddHashToken(installer.InstallerFile, installer.HashToken);
    258             }
    259 
    260             // Extra steps.
    261             // An msix can include the signature token.
    262             if (installer.Type == InstallerType.Msix)
    263             {
    264                 if (!string.IsNullOrEmpty(installer.SignatureToken))
    265                 {
    266                     var signatureFilePath = Helpers.GetSignatureFileFromMsix(installer.InstallerFile);
    267                     this.tokens.AddHashToken(signatureFilePath, installer.SignatureToken);
    268 
    269                     try
    270                     {
    271                         var dir = Path.GetDirectoryName(signatureFilePath);
    272                         if (!string.IsNullOrEmpty(dir))
    273                         {
    274                             Directory.Delete(dir, true);
    275                         }
    276                     }
    277                     catch (Exception)
    278                     {
    279                     }
    280                 }
    281             }
    282         }
    283 
    284         private Signature? GetSignature(Installer installer)
    285         {
    286             if (installer.Type == InstallerType.Zip)
    287             {
    288                 return null;
    289             }
    290 
    291             return installer.Signature == null ? this.signature : installer.Signature;
    292         }
    293     }
    294 }