Helpers.cs (7385B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 4 namespace Microsoft.WinGetSourceCreator 5 { 6 using global::WinGetSourceCreator.Model; 7 using Microsoft.Msix.Utils.ProcessRunner; 8 using System.Diagnostics; 9 using System.Xml; 10 11 internal static class Helpers 12 { 13 public static void SignInstaller(SourceInstaller installer, Signature signature) 14 { 15 if (installer.Type == InstallerType.Msix) 16 { 17 SignMsixFile(installer.InstallerFile, signature); 18 } 19 else 20 { 21 SignFile(installer.InstallerFile, signature); 22 } 23 } 24 25 public static void SignFile(string fileToSign, Signature signature) 26 { 27 if (!File.Exists(fileToSign)) 28 { 29 throw new FileNotFoundException(fileToSign); 30 } 31 32 if (!File.Exists(signature.CertFile)) 33 { 34 throw new FileNotFoundException(signature.CertFile); 35 } 36 37 string pathToSDK = SDKDetector.Instance.LatestSDKBinPath; 38 string signtoolExecutable = Path.Combine(pathToSDK, "signtool.exe"); 39 string command = $"sign /a /fd sha256 /f {signature.CertFile} "; 40 if (!string.IsNullOrEmpty(signature.Password)) 41 { 42 command += $"/p {signature.Password} "; 43 } 44 command += fileToSign; 45 RunCommand(signtoolExecutable, command); 46 } 47 48 public static void SignMsixFile(string fileToSign, Signature signature) 49 { 50 if (!File.Exists(fileToSign)) 51 { 52 throw new FileNotFoundException(fileToSign); 53 } 54 55 // Modify publisher if needed. 56 if (signature.Publisher != null) 57 { 58 string tmpPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); 59 Unpack(fileToSign, tmpPath); 60 ModifyAppxManifestIdentity(Path.Combine(tmpPath, "AppxManifest.xml"), signature.Publisher); 61 Pack(fileToSign, tmpPath); 62 63 try 64 { 65 Directory.Delete(tmpPath, true); 66 } 67 catch (Exception) 68 { 69 } 70 } 71 72 SignFile(fileToSign, signature); 73 } 74 75 public static void Unpack(string package, string outDir) 76 { 77 if (!File.Exists(package)) 78 { 79 throw new FileNotFoundException(package); 80 } 81 82 string pathToSDK = SDKDetector.Instance.LatestSDKBinPath; 83 string makeappxExecutable = Path.Combine(pathToSDK, "makeappx.exe"); 84 string args = $"unpack /nv /p {package} /d {outDir}"; 85 Process p = new Process 86 { 87 StartInfo = new ProcessStartInfo(makeappxExecutable, args) 88 }; 89 p.Start(); 90 p.WaitForExit(); 91 } 92 93 public static void PackWithMappingFile(string outputPackage, string mappingFile) 94 { 95 if (!File.Exists(mappingFile)) 96 { 97 throw new FileNotFoundException(mappingFile); 98 } 99 100 string pathToSDK = SDKDetector.Instance.LatestSDKBinPath; 101 string makeappxExecutable = Path.Combine(pathToSDK, "makeappx.exe"); 102 string args = $"pack /o /nv /f {mappingFile} /p {outputPackage}"; 103 RunCommand(makeappxExecutable, args); 104 } 105 106 public static void Pack(string outputPackage, string directoryToPack) 107 { 108 if (!Directory.Exists(directoryToPack)) 109 { 110 throw new DirectoryNotFoundException(directoryToPack); 111 } 112 113 if (File.Exists(outputPackage)) 114 { 115 File.Delete(outputPackage); 116 } 117 118 string pathToSDK = SDKDetector.Instance.LatestSDKBinPath; 119 string makeappxExecutable = Path.Combine(pathToSDK, "makeappx.exe"); 120 string args = $"pack /o /d {directoryToPack} /p {outputPackage}"; 121 RunCommand(makeappxExecutable, args); 122 } 123 124 public static void RunCommand(string command, string args, string? workingDirectory = null) 125 { 126 Process p = new() 127 { 128 StartInfo = new ProcessStartInfo(command, args) 129 }; 130 131 if (workingDirectory != null) 132 { 133 p.StartInfo.WorkingDirectory = workingDirectory; 134 } 135 p.Start(); 136 p.WaitForExit(); 137 } 138 139 // If in the future we edit more elements, this should be a nice wrapper class. 140 public static void ModifyAppxManifestIdentity(string manifestFile, string? identityPublisher) 141 { 142 if (!File.Exists(manifestFile)) 143 { 144 throw new FileNotFoundException(manifestFile); 145 } 146 147 var xmlDoc = new XmlDocument(); 148 XmlNamespaceManager namespaces = new XmlNamespaceManager(xmlDoc.NameTable); 149 namespaces.AddNamespace("n", "http://schemas.microsoft.com/appx/manifest/foundation/windows10"); 150 xmlDoc.Load(manifestFile); 151 var identityNode = xmlDoc.SelectSingleNode("/n:Package/n:Identity", namespaces); 152 if (identityNode == null) 153 { 154 throw new NullReferenceException("Identity node"); 155 } 156 157 if (!string.IsNullOrEmpty(identityPublisher)) 158 { 159 var attr = identityNode.Attributes?["Publisher"]; 160 if (attr == null) 161 { 162 throw new NullReferenceException("Publisher attribute"); 163 } 164 attr.Value = identityPublisher; 165 } 166 167 xmlDoc.Save(manifestFile); 168 } 169 170 // Gets the AppxSignature.p7x file. 171 public static string GetSignatureFileFromMsix(string packageFilePath) 172 { 173 string extractedPackageDest = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); 174 if (Directory.Exists(extractedPackageDest)) 175 { 176 Directory.Delete(extractedPackageDest, true); 177 } 178 179 Helpers.Unpack(packageFilePath, extractedPackageDest); 180 181 return Path.Combine(extractedPackageDest, "AppxSignature.p7x"); 182 } 183 184 public static void CopyDirectory(string sourceDirName, string destDirName) 185 { 186 if (!Directory.Exists(destDirName)) 187 { 188 Directory.CreateDirectory(destDirName); 189 } 190 191 DirectoryInfo dir = new (sourceDirName); 192 DirectoryInfo[] dirs = dir.GetDirectories(); 193 194 FileInfo[] files = dir.GetFiles(); 195 foreach (FileInfo file in files) 196 { 197 string temppath = Path.Combine(destDirName, file.Name); 198 file.CopyTo(temppath, false); 199 } 200 201 foreach (DirectoryInfo subdir in dirs) 202 { 203 string temppath = Path.Combine(destDirName, subdir.Name); 204 CopyDirectory(subdir.FullName, temppath); 205 } 206 } 207 } 208 }