├── .gitattributes ├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── .gitmodules ├── Docs ├── Donations.md ├── GettingWoA.md ├── Lumia.md ├── Raspberry.md ├── mcci_donate.md └── mcci_license.md ├── Installer.Core.FullFx ├── DefaultServiceFactory.cs ├── DismImageService.cs ├── ImageFlasher.cs ├── ImageServiceBase.cs ├── Installer.Core.FullFx.csproj ├── InvalidImageException.cs ├── LowLevelApi.cs ├── PowerShellUtils.cs ├── Properties │ └── AssemblyInfo.cs └── WimlibImageService.cs ├── Installer.Core.Raspberry ├── Installer.Raspberry.Core.csproj ├── RaspberryPi.cs ├── RaspberryPiDeployer.cs └── RaspberryPiWindowsDeployer.cs ├── Installer.Core ├── Device.cs ├── Exceptions │ ├── DeploymentException.cs │ ├── ExtractionException.cs │ ├── InvalidDeploymentRepositoryException.cs │ ├── InvalidWimFileException.cs │ ├── NotEnoughSpaceException.cs │ ├── PathNotFoundException.cs │ └── PhoneDiskNotFoundException.cs ├── FileSystem │ ├── Disk.cs │ ├── DiskInfo.cs │ ├── DriverMetadata.cs │ ├── FileSystemFormat.cs │ ├── FileSystemMixin.cs │ ├── ILowLevelApi.cs │ ├── Partition.cs │ ├── PartitionType.cs │ └── Volume.cs ├── ICoreDeployer.cs ├── IDeployer.cs ├── InstallOptions.cs ├── Installer.Core.csproj ├── Resources.Designer.cs ├── Resources.resx ├── ServiceFactory.cs ├── Services │ ├── ArchiveUncompressor.cs │ ├── BcdConfigurator.cs │ ├── BcdInvoker.cs │ ├── DeploymentPaths.cs │ ├── DiskService.cs │ ├── IArchiveUncompressor.cs │ ├── IImageFlasher.cs │ ├── IPackageImporter.cs │ ├── IWindowsDeployer.cs │ ├── IWindowsImageService.cs │ ├── Packages │ │ └── PackageImporter.cs │ ├── SystemPaths.cs │ ├── Wim │ │ ├── DiskImageMetadata.cs │ │ ├── IWindowsImageMetadataReader.cs │ │ ├── ImageMetadata.cs │ │ ├── Languages.cs │ │ ├── ServicingData.cs │ │ ├── Time.cs │ │ ├── Version.cs │ │ ├── WimMetadata.cs │ │ ├── Windows.cs │ │ ├── WindowsImageMetadataReader.cs │ │ ├── WindowsImageMetadataReaderBase.cs │ │ ├── XmlWindowsImageMetadata.cs │ │ └── XmlWindowsImageMetadataReader.cs │ └── WindowsVolumes.cs └── Utils │ ├── FileUtils.cs │ ├── FormattingUtils.cs │ ├── ProcessUtils.cs │ └── ReactiveMixin.cs ├── Installer.Raspberry.Application ├── App.config ├── App.xaml ├── App.xaml.cs ├── Assets │ ├── appicon.ico │ ├── appicon.png │ ├── disabledual.png │ ├── drive.png │ ├── dual.png │ ├── full.png │ ├── gears.png │ ├── heart.png │ ├── inject-drivers.png │ ├── refresh.png │ ├── windowicon.png │ └── windows-only.png ├── Installer.Raspberry.Application.csproj ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ └── Resources.resx ├── Settings.Designer.cs ├── Settings.settings ├── SettingsService.cs ├── Views │ ├── CompositionRoot.cs │ ├── MainWindow.xaml │ ├── MainWindow.xaml.cs │ ├── Parts │ │ ├── LogPart.xaml │ │ ├── LogPart.xaml.cs │ │ ├── MarkdownViewerWindow.xaml │ │ ├── MarkdownViewerWindow.xaml.cs │ │ ├── Sections │ │ │ ├── AdvancedPart.xaml │ │ │ ├── AdvancedPart.xaml.cs │ │ │ ├── DiskSelectionPart.xaml │ │ │ ├── DiskSelectionPart.xaml.cs │ │ │ ├── InstallPart.xaml │ │ │ ├── InstallPart.xaml.cs │ │ │ ├── WimOptionsPart.xaml │ │ │ └── WimOptionsPart.xaml.cs │ │ ├── StatusPart.xaml │ │ └── StatusPart.xaml.cs │ ├── TextViewerWindow.xaml │ └── TextViewerWindow.xaml.cs └── app.manifest ├── Installer.Tests ├── DismImageServiceSpecs.cs ├── Installer.Tests.csproj ├── LowLevelApiSpecs.cs ├── PhoneSpecs.cs ├── Properties │ └── AssemblyInfo.cs ├── WimImageMetadataReaderSpecs.cs ├── WindowsDeployerTests.cs ├── packages.config └── test.xml ├── Installer.UI ├── FilePickerMixin.cs ├── FileTypeFilter.cs ├── IDialogService.cs ├── IFilePicker.cs ├── IViewService.cs ├── Installer.UI.csproj └── UIServices.cs ├── Installer.ViewModels.Core ├── CommandWrapper.cs ├── IPackageImporterFactory.cs ├── Installer.ViewModels.Core.csproj ├── MessageViewModel.cs ├── PackageImporterFactory.cs ├── RenderedLogEvent.cs └── WimMetadataViewModel.cs ├── Installer.ViewModels.Raspberry ├── DiskViewModel.cs ├── ISettingsService.cs ├── Installer.Raspberry.ViewModels.csproj ├── Installer.Raspberry.ViewModels.csproj.DotSettings ├── Installer.ViewModels.Raspberry.csproj.DotSettings ├── MainViewModel.cs ├── Resources.Designer.cs └── Resources.resx ├── Installer.ViewModels ├── DeployerItem.cs ├── DualBootViewModel.cs ├── Gpu.md ├── ISettingsService.cs ├── Installer.Lumia.ViewModels.csproj ├── MainViewModel.cs ├── PhoneModel.cs ├── Resources.Designer.cs └── Resources.resx ├── Installer.Wpf.Core ├── BusyButton.cs ├── Installer.Wpf.Core.csproj ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Settings.Designer.cs │ └── Settings.settings ├── Services │ ├── DialogService.cs │ ├── FilePicker.cs │ └── ViewService.cs └── Themes │ └── Generic.xaml ├── LICENSE ├── README.md └── WoA Installer.sln /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Please use the "Preview" tab above to view this message if you are seeing this in the new issue text box. 2 | 3 | Please note that this is a **WoA Installer** issue tracker, use this issue tracker only for the WoA Installer related bugs and enhancements. 4 | 5 | Please use the [LumiaWoA](https://t.me/joinchat/Ey6mehEPg0Fe4utQNZ9yjA) or [RaspberryPiWOA](https://t.me/raspberrypiwoa) Telegram groups for anything else, such as: 6 | 7 | - Reporting bugs of other tools, drivers, etc. 8 | - Asking for help 9 | - Asking about project progress. 10 | 11 | # Please remove the text above before opening an issue. 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Registry"] 2 | path = Registry 3 | url = https://github.com/EricZimmerman/Registry.git 4 | -------------------------------------------------------------------------------- /Docs/Donations.md: -------------------------------------------------------------------------------- 1 |  2 | # Support my work 3 | 4 | If you find this useful, feel free to [buy me a coffee ☕](http://paypal.me/superjmn). Thanks in advance!! 5 | 6 | ## Donate to contributors of the RaspberryPi WOA project 7 | Please, don't forget that the RaspberryPi WOA Project is supported by other individuals and companies (see the [credits and acknowledgements section](#credits-and-acknowledgements )). 8 | - Donate to MCCI. Why? [Read this 🗒](mcci_donate.md) -------------------------------------------------------------------------------- /Docs/GettingWoA.md: -------------------------------------------------------------------------------- 1 | # Getting Windows 10 ARM 2 | 1. Go to this site: https://uup.rg-adguard.net/ 3 | 2. Enter these options: 4 | ![image](https://user-images.githubusercontent.com/3109851/51803240-f5528780-2252-11e9-92e8-2d80169e1131.png) 5 | * Please, notice the we selected the **ARM64** architecture (not to confuse with AMD64) 6 | * You can select a newer build if you wish. Take into account, however, that each build can have some issues. It's recommended to select builds from the R5+ branch. Build numbers like 18XXX are usually OK. 7 | 3. Click the link on the right. It will appear when you've done selecting the options. It will download a .zip file. 8 | 4. Extract the .zip file to a Folder, preferably one that doesn't contain spaces in the path, like "c:\temp\W10IsoScripts" 9 | 5. Execute the script **creatingISO.cmd** and wait for it to complete. 10 | 6. When the script has finished, you will find a **.iso ** inside, as a result of the process. 11 | 7. Mount the .iso file with Windows Explorer by double clicking it. 12 | 8. Navigate to the folder x:\sources, where **x:** is the drive letter of the mounted .iso file. 13 | 9. Inside this "sources" folder you will find the **install.wim** that WoA Installer needs to deploy Windows 😃 14 | -------------------------------------------------------------------------------- /Docs/Lumia.md: -------------------------------------------------------------------------------- 1 | # WoA Installer for Lumia 950/XL 2 | 3 | ## IMPORTANT NEWS! 4 | 5 | ❗ This application has been replaced by **WOA Deployer**, the new iteration. 6 | 7 | Go to [WOA Deployer's Site](https://github.com/WOA-project/WOA-Deployer) 8 | -------------------------------------------------------------------------------- /Docs/Raspberry.md: -------------------------------------------------------------------------------- 1 | # WoA Installer for Raspberry Pi 3 2 | 3 | Please, follow [this link](/README.md) to the main site -------------------------------------------------------------------------------- /Docs/mcci_donate.md: -------------------------------------------------------------------------------- 1 | # About MCCI 2 | 3 | I'm Terry Moore, CEO of MCCI. 4 | 5 | We're the company that created the USB software inside your Raspberry Pi 3. 6 | 7 | Among other things, we ported the USB drivers for the RaspberryPi WOA project 😊 8 | 9 | ## Supporting our work 10 | 11 | If the drivers work for you (and I'm sure they did 😉), please consider a donation to [The Things Network New York, Inc](https://thethings.nyc). This is a non-profit 501(c)(3) public charity that MCCI leads in NYC and Ithaca; we are working to deploy community owned, open source, standards-based, free-to-use IoT networks for remote sensing and smart city applications. 12 | 13 | There’s a **“Donate”** link on the TTN NY home page or you can click [this link](https://squareup.com/store/the-things-network-new-york-inc) to go to the donation page. 14 | 15 | Note that this doesn’t go to fund MCCI, but rather it goes to fund a cause that MCCI cares a lot about; your contribution will help them (and everyone doing community LoRaWAN networking) in their effort to make IoT a public good. 16 | 17 | # The Things Network 18 | 19 | The Things Network lets independent municipal leaders like myself (Gale A. Brewer, Manhattan Borough President) walk down a path that was previously unavailable to government, a third way between a municipal build out of a Wide Area Network and a massive franchise agreement with a private corporation. Together with The Things Network, government may now organize in concert with a diverse coalition of partners to create and enhance services at the local community level. 20 | 21 | New York can lead by example, showing American technologists that there is more than one way to do an IoT rollout. They don’t need to be done from the top down, with a government franchise and loads of commoditized data. -------------------------------------------------------------------------------- /Installer.Core.FullFx/DefaultServiceFactory.cs: -------------------------------------------------------------------------------- 1 | using Installer.Core.Services; 2 | 3 | namespace Installer.Core.FullFx 4 | { 5 | public class DefaultServiceFactory : ServiceFactory 6 | { 7 | public DefaultServiceFactory() 8 | { 9 | DiskService = new DiskService(new LowLevelApi()); 10 | ImageService = new DismImageService(); 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /Installer.Core.FullFx/DismImageService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Reactive.Linq; 4 | using System.Reactive.Subjects; 5 | using System.Text.RegularExpressions; 6 | using System.Threading.Tasks; 7 | using Installer.Core.Exceptions; 8 | using Installer.Core.FileSystem; 9 | using Installer.Core.Services; 10 | using Installer.Core.Utils; 11 | using Serilog; 12 | 13 | namespace Installer.Core.FullFx 14 | { 15 | public class DismImageService : ImageServiceBase 16 | { 17 | private readonly Regex percentRegex = new Regex(@"(\d*.\d*)%"); 18 | 19 | public override async Task ApplyImage(Volume volume, string imagePath, int imageIndex = 1, IObserver progressObserver = null) 20 | { 21 | EnsureValidParameters(volume, imagePath, imageIndex); 22 | 23 | ISubject outputSubject = new Subject(); 24 | IDisposable stdOutputSubscription = null; 25 | if (progressObserver != null) 26 | { 27 | stdOutputSubscription = outputSubject 28 | .Select(GetPercentage) 29 | .Where(d => !double.IsNaN(d)) 30 | .Subscribe(progressObserver); 31 | } 32 | 33 | var dismName = SystemPaths.Dism; 34 | var args = $@"/Apply-Image /compact /ImageFile:""{imagePath}"" /Index:{imageIndex} /ApplyDir:{volume.RootDir.Name}"; 35 | 36 | Log.Verbose("We are about to run DISM: {ExecName} {Parameters}", dismName, args); 37 | var resultCode = await ProcessUtils.RunProcessAsync(dismName, args, outputObserver: outputSubject); 38 | if (resultCode != 0) 39 | { 40 | throw new DeploymentException($"There has been a problem during deployment: DISM exited with code {resultCode}."); 41 | } 42 | 43 | stdOutputSubscription?.Dispose(); 44 | } 45 | 46 | private double GetPercentage(string dismOutput) 47 | { 48 | if (dismOutput == null) 49 | { 50 | return double.NaN; 51 | } 52 | 53 | var matches = percentRegex.Match(dismOutput); 54 | 55 | if (matches.Success) 56 | { 57 | var value = matches.Groups[1].Value; 58 | try 59 | { 60 | var percentage = double.Parse(value, CultureInfo.InvariantCulture) / 100D; 61 | return percentage; 62 | } 63 | catch (FormatException) 64 | { 65 | Log.Warning($"Cannot convert {value} to double"); 66 | } 67 | } 68 | 69 | return double.NaN; 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /Installer.Core.FullFx/ImageFlasher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.IO; 4 | using System.Reactive.Linq; 5 | using System.Reactive.Subjects; 6 | using System.Text.RegularExpressions; 7 | using System.Threading.Tasks; 8 | using Installer.Core.Exceptions; 9 | using Installer.Core.FileSystem; 10 | using Installer.Core.Services; 11 | using Installer.Core.Utils; 12 | using Serilog; 13 | 14 | namespace Installer.Core.FullFx 15 | { 16 | public class ImageFlasher : IImageFlasher 17 | { 18 | private readonly Regex percentRegex = new Regex(@"(\d*.\d*)%"); 19 | 20 | public async Task Flash(Disk disk, string imagePath, IObserver progressObserver = null) 21 | { 22 | ISubject outputSubject = new Subject(); 23 | IDisposable stdOutputSubscription = null; 24 | bool isValidating = false; 25 | if (progressObserver != null) 26 | { 27 | stdOutputSubscription = outputSubject 28 | .Do(s => 29 | { 30 | if (!isValidating && CultureInfo.CurrentCulture.CompareInfo.IndexOf(s, "validating", 0, CompareOptions.IgnoreCase) != -1) 31 | { 32 | Log.Information("Validating flashed image..."); 33 | isValidating = true; 34 | } 35 | }) 36 | .Select(GetPercentage) 37 | .Where(d => !double.IsNaN(d)) 38 | .Subscribe(progressObserver); 39 | } 40 | 41 | //etcher.exe -d \\.\PHYSICALDRIVE3 "..\Tutorial Googulator\gpt.zip" --yes 42 | var gptSchemeImagePath = Path.Combine("Files", "Core", "Gpt.zip"); 43 | 44 | var platformSuffix = Environment.Is64BitProcess ? "x64" : "x86"; 45 | var etcherPath = Path.Combine("Files", "Tools", "Etcher-Cli", platformSuffix, "Etcher"); 46 | var args = $@"-d \\.\PHYSICALDRIVE{disk.Number} ""{gptSchemeImagePath}"" --yes --no-unmount"; 47 | Log.Verbose("We are about to run Etcher: {ExecName} {Parameters}", etcherPath, args); 48 | var resultCode = await ProcessUtils.RunProcessAsync(etcherPath, args, outputObserver: outputSubject); 49 | if (resultCode != 0) 50 | { 51 | throw new DeploymentException($"There has been a problem during deployment: Etcher exited with code {resultCode}."); 52 | } 53 | 54 | progressObserver?.OnNext(double.NaN); 55 | 56 | stdOutputSubscription?.Dispose(); 57 | } 58 | 59 | private double GetPercentage(string output) 60 | { 61 | if (output == null) 62 | { 63 | return double.NaN; 64 | } 65 | 66 | var matches = percentRegex.Match(output); 67 | 68 | if (matches.Success) 69 | { 70 | var value = matches.Groups[1].Value; 71 | try 72 | { 73 | var percentage = double.Parse(value, CultureInfo.InvariantCulture) / 100D; 74 | return percentage; 75 | } 76 | catch (FormatException) 77 | { 78 | Log.Warning($"Cannot convert {value} to double"); 79 | } 80 | } 81 | 82 | return double.NaN; 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /Installer.Core.FullFx/ImageServiceBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Reactive.Subjects; 5 | using System.Runtime.InteropServices; 6 | using System.Threading.Tasks; 7 | using Installer.Core.Exceptions; 8 | using Installer.Core.FileSystem; 9 | using Installer.Core.Services; 10 | using Installer.Core.Services.Wim; 11 | using Installer.Core.Utils; 12 | using Serilog; 13 | 14 | namespace Installer.Core.FullFx 15 | { 16 | public abstract class ImageServiceBase : IWindowsImageService 17 | { 18 | public abstract Task ApplyImage(Volume volume, string imagePath, int imageIndex = 1, 19 | IObserver progressObserver = null); 20 | 21 | protected static void EnsureValidParameters(Volume volume, string imagePath, int imageIndex) 22 | { 23 | if (volume == null) 24 | { 25 | throw new ArgumentNullException(nameof(volume)); 26 | } 27 | 28 | var applyDir = volume.RootDir.Name; 29 | 30 | if (applyDir == null) 31 | { 32 | throw new ArgumentException("The volume to apply the image is invalid"); 33 | } 34 | 35 | if (imagePath == null) 36 | { 37 | throw new ArgumentNullException(nameof(imagePath)); 38 | } 39 | 40 | EnsureValidImage(imagePath, imageIndex); 41 | } 42 | 43 | private static void EnsureValidImage(string imagePath, int imageIndex) 44 | { 45 | Log.Verbose("Checking image at {Path}, with index {Index}", imagePath, imagePath); 46 | 47 | if (!File.Exists(imagePath)) 48 | { 49 | throw new FileNotFoundException($"Image not found: {imagePath}. Please, verify that the file exists and it's accessible."); 50 | } 51 | 52 | Log.Verbose("Image file at '{ImagePath}' exists", imagePath); 53 | 54 | using (var stream = File.OpenRead(imagePath)) 55 | { 56 | var metadata = new WindowsImageMetadataReader().Load(stream); 57 | var imageMetadata = metadata.Images.Single(x => x.Index == imageIndex); 58 | if (imageMetadata.Architecture != Architecture.Arm64) 59 | { 60 | throw new InvalidImageException("The selected image isn't for the ARM64 architecture."); 61 | } 62 | } 63 | } 64 | 65 | public async Task InjectDrivers(string path, Volume volume) 66 | { 67 | var outputSubject = new Subject(); 68 | var subscription = outputSubject.Subscribe(Log.Verbose); 69 | var resultCode = await ProcessUtils.RunProcessAsync(SystemPaths.Dism, $@"/Add-Driver /Image:{volume.RootDir.Name} /Driver:""{path}"" /Recurse /ForceUnsigned", outputSubject, outputSubject); 70 | subscription.Dispose(); 71 | 72 | if (resultCode != 0) 73 | { 74 | throw new DeploymentException( 75 | $"There has been a problem during deployment: DISM exited with code {resultCode}."); 76 | } 77 | } 78 | 79 | public async Task RemoveDriver(string path, Volume volume) 80 | { 81 | var outputSubject = new Subject(); 82 | var subscription = outputSubject.Subscribe(Log.Verbose); 83 | var resultCode = await ProcessUtils.RunProcessAsync(SystemPaths.Dism, $@"/Remove-Driver /Image:{volume.RootDir.Name} /Driver:""{path}""", outputSubject, outputSubject); 84 | subscription.Dispose(); 85 | 86 | if (resultCode != 0) 87 | { 88 | throw new DeploymentException( 89 | $"There has been a problem during removal: DISM exited with code {resultCode}."); 90 | } 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /Installer.Core.FullFx/Installer.Core.FullFx.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {15D1B19B-6F1B-47AD-BE7B-F9BCA447CBC0} 8 | Library 9 | Properties 10 | Installer.Core.FullFx 11 | Installer.Core.FullFx 12 | v4.6.1 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | {97CF13C2-67D8-4BDF-80D3-00E31685603F} 57 | Installer.Core 58 | 59 | 60 | {5c40be24-a89d-4973-b668-2d6e945cb047} 61 | Registry 62 | 63 | 64 | 65 | 66 | 1.0.0 67 | 68 | 69 | 1.1.0 70 | 71 | 72 | 2.7.1 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /Installer.Core.FullFx/InvalidImageException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Installer.Core.FullFx 4 | { 5 | internal class InvalidImageException : Exception 6 | { 7 | public InvalidImageException(string msg) : base(msg) 8 | { 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /Installer.Core.FullFx/PowerShellUtils.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.PowerShell.Cim; 2 | 3 | namespace Installer.Core.FullFx 4 | { 5 | public static class PowerShellUtils 6 | { 7 | private static readonly CimInstanceAdapter Adapter = new CimInstanceAdapter(); 8 | 9 | public static object GetPropertyValue(this object obj, string propertyName) 10 | { 11 | var psAdaptedProperty = Adapter.GetProperty(obj, propertyName); 12 | if (psAdaptedProperty == null) 13 | { 14 | return null; 15 | } 16 | 17 | return Adapter.GetPropertyValue(psAdaptedProperty); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /Installer.Core.FullFx/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // La información general de un ensamblado se controla mediante el siguiente 6 | // conjunto de atributos. Cambie estos valores de atributo para modificar la información 7 | // asociada con un ensamblado. 8 | [assembly: AssemblyTitle("Installer.Core.FullFx")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Installer.Core.FullFx")] 13 | [assembly: AssemblyCopyright("Copyright © 2018")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Si establece ComVisible en false, los tipos de este ensamblado no estarán visibles 18 | // para los componentes COM. Si es necesario obtener acceso a un tipo en este ensamblado desde 19 | // COM, establezca el atributo ComVisible en true en este tipo. 20 | [assembly: ComVisible(false)] 21 | 22 | // El siguiente GUID sirve como id. de typelib si este proyecto se expone a COM. 23 | [assembly: Guid("15d1b19b-6f1b-47ad-be7b-f9bca447cbc0")] 24 | 25 | // La información de versión de un ensamblado consta de los cuatro valores siguientes: 26 | // 27 | // Versión principal 28 | // Versión secundaria 29 | // Número de compilación 30 | // Revisión 31 | // 32 | // Puede especificar todos los valores o usar los números de compilación y de revisión predeterminados 33 | // mediante el carácter "*", como se muestra a continuación: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Installer.Core.FullFx/WimlibImageService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Installer.Core.FileSystem; 4 | using ManagedWimLib; 5 | 6 | namespace Installer.Core.FullFx 7 | { 8 | public class WimlibImageService : ImageServiceBase 9 | { 10 | public override async Task ApplyImage(Volume volume, string imagePath, int imageIndex = 1, IObserver progressObserver = null) 11 | { 12 | EnsureValidParameters(volume, imagePath, imageIndex); 13 | 14 | await Task.Run(() => 15 | { 16 | using (var wim = Wim.OpenWim(imagePath, OpenFlags.DEFAULT, (msg, info, callback) => UpdatedStatusCallback(msg, info, callback, progressObserver))) 17 | { 18 | wim.ExtractImage(imageIndex, volume.RootDir.Name, ExtractFlags.DEFAULT); 19 | } 20 | }); 21 | } 22 | 23 | private static CallbackStatus UpdatedStatusCallback(ProgressMsg msg, object info, object progctx, 24 | IObserver progressObserver) 25 | { 26 | if (info is ProgressInfo_Extract m) 27 | { 28 | ulong percentComplete = 0; 29 | 30 | switch (msg) 31 | { 32 | case ProgressMsg.EXTRACT_FILE_STRUCTURE: 33 | 34 | if (0 < m.EndFileCount) 35 | { 36 | percentComplete = m.CurrentFileCount * 10 / m.EndFileCount; 37 | } 38 | 39 | break; 40 | case ProgressMsg.EXTRACT_STREAMS: 41 | 42 | if (0 < m.TotalBytes) 43 | { 44 | percentComplete = 10 + m.CompletedBytes * 80 / m.TotalBytes; 45 | } 46 | 47 | break; 48 | case ProgressMsg.EXTRACT_METADATA: 49 | 50 | if (0 < m.EndFileCount) 51 | { 52 | percentComplete = 90 + m.CurrentFileCount * 10 / m.EndFileCount; 53 | } 54 | 55 | break; 56 | } 57 | 58 | progressObserver.OnNext((double)percentComplete / 100); 59 | } 60 | 61 | 62 | return CallbackStatus.CONTINUE; 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /Installer.Core.Raspberry/Installer.Raspberry.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Installer.Core.Raspberry/RaspberryPi.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Installer.Core; 3 | using Installer.Core.FileSystem; 4 | 5 | namespace Installer.Raspberry.Core 6 | { 7 | public class RaspberryPi : Device 8 | { 9 | public RaspberryPi(Disk disk) : base(disk) 10 | { 11 | } 12 | 13 | public override async Task RemoveExistingWindowsPartitions() 14 | { 15 | await Task.CompletedTask; 16 | } 17 | 18 | public override Task GetBootVolume() 19 | { 20 | return GetVolume("EFIESP"); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /Installer.Core.Raspberry/RaspberryPiDeployer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | using Installer.Core; 5 | using Installer.Core.Exceptions; 6 | using Installer.Core.Services; 7 | using Installer.Core.Utils; 8 | using Serilog; 9 | 10 | namespace Installer.Raspberry.Core 11 | { 12 | public class RaspberryPiDeployer : IDeployer 13 | { 14 | private readonly IImageFlasher flasher; 15 | private readonly IWindowsDeployer windowsDeployer; 16 | 17 | public RaspberryPiDeployer(IImageFlasher flasher, IWindowsDeployer windowsDeployer) 18 | { 19 | this.flasher = flasher; 20 | this.windowsDeployer = windowsDeployer; 21 | } 22 | 23 | public async Task DeployCoreAndWindows(InstallOptions options, RaspberryPi device, IObserver progressObserver = null) 24 | { 25 | await EnsureValidCoreWindowsDeployment(); 26 | 27 | await CreateInitialPartitionLayout(device, progressObserver); 28 | await DeployUefi(device); 29 | await DeployWindows(options, device, progressObserver); 30 | } 31 | 32 | private async Task EnsureValidCoreWindowsDeployment() 33 | { 34 | var windowsValid = await windowsDeployer.AreDeploymentFilesValid(); 35 | 36 | var driverPaths = new[] 37 | { 38 | Path.Combine("Files", "Core"), 39 | Path.Combine("Files", "UEFI"), 40 | Path.Combine("Files", "Drivers"), 41 | Path.Combine("Files", "Tools"), 42 | }; 43 | 44 | var coreValid = driverPaths.EnsureExistingPaths(); 45 | 46 | var isValid = coreValid && windowsValid; 47 | if (!isValid) 48 | { 49 | throw new InvalidDeploymentRepositoryException("The Files repository doesn't contain the required files. Please, check that you've installed a valid Core Package"); 50 | } 51 | } 52 | 53 | private async Task CreateInitialPartitionLayout(RaspberryPi device, IObserver progressObserver) 54 | { 55 | Log.Information("Flashing GPT image..."); 56 | await flasher.Flash(device.Disk, @"Files\Core\gpt.zip", progressObserver); 57 | Log.Information("GPT image flashed"); 58 | } 59 | 60 | private async Task DeployUefi(Device device) 61 | { 62 | var efiesp = await device.GetBootVolume(); 63 | await FileUtils.CopyDirectory(new DirectoryInfo(Path.Combine("Files", "UEFI")), efiesp.RootDir); 64 | } 65 | 66 | public async Task DeployWindows(InstallOptions options, RaspberryPi device, IObserver progressObserver = null) 67 | { 68 | await windowsDeployer.Deploy(options, device, progressObserver); 69 | } 70 | 71 | public Task InjectPostOobeDrivers(RaspberryPi device) 72 | { 73 | throw new NotImplementedException(); 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /Installer.Core/Device.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Installer.Core.FileSystem; 7 | using Registry; 8 | using Serilog; 9 | 10 | namespace Installer.Core 11 | { 12 | public abstract class Device 13 | { 14 | protected Device(Disk disk) 15 | { 16 | Disk = disk; 17 | } 18 | 19 | public Disk Disk { get; } 20 | 21 | protected async Task GetVolume(string label) 22 | { 23 | Log.Verbose("Getting {Label} volume", label); 24 | 25 | var volumes = await Disk.GetVolumes(); 26 | 27 | var volume = volumes.SingleOrDefault(v => string.Equals(v.Label, label, StringComparison.InvariantCultureIgnoreCase)); 28 | 29 | if (volume == null) 30 | { 31 | return null; 32 | } 33 | 34 | if (volume.Letter != null) 35 | { 36 | return volume; 37 | } 38 | 39 | Log.Verbose("{Label} volume wasn't mounted.", label); 40 | await volume.Mount(); 41 | 42 | return volume; 43 | } 44 | 45 | public async Task GetWindowsVolume() 46 | { 47 | return await GetVolume("WindowsARM"); 48 | } 49 | 50 | protected async Task IsWoAPresent() 51 | { 52 | try 53 | { 54 | await IsBootVolumePresent(); 55 | await GetWindowsVolume(); 56 | } 57 | catch (Exception e) 58 | { 59 | Log.Error(e, "Failed to get WoA's volumes"); 60 | return false; 61 | } 62 | 63 | return true; 64 | } 65 | 66 | private async Task IsBootVolumePresent() 67 | { 68 | var bootPartition = await GetBootPartition(); 69 | 70 | if (bootPartition != null) 71 | { 72 | return true; 73 | } 74 | 75 | var bootVolume = await GetBootVolume(); 76 | return bootVolume != null; 77 | } 78 | 79 | public abstract Task GetBootVolume(); 80 | 81 | protected async Task IsWindowsPhonePresent() 82 | { 83 | try 84 | { 85 | await GetVolume("MainOS"); 86 | await GetVolume("Data"); 87 | } 88 | catch (Exception e) 89 | { 90 | Log.Error(e, "Failed to get Windows Phones's volumes"); 91 | return false; 92 | } 93 | 94 | return true; 95 | } 96 | 97 | protected async Task GetBootPartition() 98 | { 99 | var partitions = await Disk.GetPartitions(); 100 | var bootPartition = partitions.FirstOrDefault(x => Equals(x.PartitionType, PartitionType.Esp)); 101 | if (bootPartition != null) 102 | { 103 | return bootPartition; 104 | } 105 | 106 | var bootVolume = await GetBootVolume(); 107 | return bootVolume?.Partition; 108 | } 109 | 110 | protected static async Task RemovePartition(string partitionName, Partition partition) 111 | { 112 | Log.Verbose("Trying to remove previously existing {Partition} partition", partitionName); 113 | if (partition != null) 114 | { 115 | Log.Verbose("{Partition} exists: Removing it...", partition); 116 | await partition.Remove(); 117 | Log.Verbose("{Partition} removed", partition); 118 | } 119 | } 120 | 121 | public async Task IsOobeFinished() 122 | { 123 | var winVolume = await GetWindowsVolume(); 124 | 125 | if (winVolume == null) 126 | { 127 | return false; 128 | } 129 | 130 | var path = Path.Combine(winVolume.RootDir.Name, "Windows", "System32", "Config", "System"); 131 | var hive = new RegistryHive(path) { RecoverDeleted = true }; 132 | hive.ParseHive(); 133 | 134 | var key = hive.GetKey("Setup"); 135 | var val = key.Values.Single(x => x.ValueName == "OOBEInProgress"); 136 | 137 | return int.Parse(val.ValueData) == 0; 138 | } 139 | 140 | public abstract Task RemoveExistingWindowsPartitions(); 141 | 142 | public async Task> GetDrivers() 143 | { 144 | var windows = await GetWindowsVolume(); 145 | return await windows.GetDrivers(); 146 | } 147 | } 148 | } -------------------------------------------------------------------------------- /Installer.Core/Exceptions/DeploymentException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Installer.Core.Exceptions 4 | { 5 | public class DeploymentException : Exception 6 | { 7 | public DeploymentException(string msg) : base(msg) 8 | { 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /Installer.Core/Exceptions/ExtractionException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Installer.Core.Exceptions 4 | { 5 | internal class ExtractionException : Exception 6 | { 7 | public ExtractionException(string msg) : base(msg) 8 | { 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /Installer.Core/Exceptions/InvalidDeploymentRepositoryException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Installer.Core.Exceptions 4 | { 5 | public class InvalidDeploymentRepositoryException : Exception 6 | { 7 | public InvalidDeploymentRepositoryException(string str) : base(str) 8 | { 9 | } 10 | 11 | public InvalidDeploymentRepositoryException(string str, Exception inner) : base(str, inner) 12 | { 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /Installer.Core/Exceptions/InvalidWimFileException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace Installer.Core.Exceptions 5 | { 6 | public class InvalidWimFileException : Exception 7 | { 8 | public InvalidWimFileException(string msg) : base(msg) 9 | { 10 | } 11 | 12 | public InvalidWimFileException(string msg, Exception innerException) : base(msg, innerException) 13 | { 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /Installer.Core/Exceptions/NotEnoughSpaceException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Installer.Core.Exceptions 4 | { 5 | public class NotEnoughSpaceException : Exception 6 | { 7 | public NotEnoughSpaceException(string msg) : base(msg) 8 | { 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /Installer.Core/Exceptions/PathNotFoundException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Installer.Core.Exceptions 4 | { 5 | internal class PathNotFoundException : Exception 6 | { 7 | public PathNotFoundException(string msg) : base(msg) 8 | { 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /Installer.Core/Exceptions/PhoneDiskNotFoundException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Installer.Core.Exceptions 4 | { 5 | public class PhoneDiskNotFoundException : Exception 6 | { 7 | public PhoneDiskNotFoundException(string message) : base(message) 8 | { 9 | 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /Installer.Core/FileSystem/Disk.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using ByteSizeLib; 5 | 6 | namespace Installer.Core.FileSystem 7 | { 8 | public class Disk 9 | { 10 | public ILowLevelApi LowLevelApi { get; } 11 | public uint Number { get; } 12 | public ByteSize Size { get; } 13 | public ByteSize AllocatedSize { get; } 14 | 15 | public Disk(ILowLevelApi lowLevelApi, DiskInfo diskProps) 16 | { 17 | LowLevelApi = lowLevelApi; 18 | FriendlyName = diskProps.FriendlyName; 19 | Number = diskProps.Number; 20 | Size = diskProps.Size; 21 | AllocatedSize = diskProps.AllocatedSize; 22 | FriendlyName = diskProps.FriendlyName; 23 | IsSystem = diskProps.IsSystem; 24 | IsBoot = diskProps.IsBoot; 25 | IsReadOnly= diskProps.IsReadOnly; 26 | IsOffline = diskProps.IsOffline; 27 | } 28 | 29 | public bool IsSystem { get; } 30 | 31 | public bool IsBoot { get; } 32 | 33 | public bool IsReadOnly { get; } 34 | 35 | public bool IsOffline { get; } 36 | 37 | public string FriendlyName { get; } 38 | 39 | public async Task> GetVolumes() 40 | { 41 | var volumes = await LowLevelApi.GetVolumes(this); 42 | return volumes; 43 | } 44 | 45 | public Task> GetPartitions() 46 | { 47 | return LowLevelApi.GetPartitions(this); 48 | } 49 | 50 | public Task CreatePartition(ulong sizeInBytes) 51 | { 52 | return LowLevelApi.CreatePartition(this, sizeInBytes); 53 | } 54 | 55 | public Task CreateReservedPartition(ulong sizeInBytes) 56 | { 57 | return LowLevelApi.CreateReservedPartition(this, sizeInBytes); 58 | } 59 | 60 | public async Task GetReservedPartition() 61 | { 62 | var parts = await LowLevelApi.GetPartitions(this); 63 | return parts.FirstOrDefault(x => Equals(x.PartitionType, PartitionType.Reserved)); 64 | } 65 | 66 | public override string ToString() 67 | { 68 | return $"{nameof(Number)}: {Number}, {nameof(Size)}: {Size.ToString()}, {nameof(AllocatedSize)}: {AllocatedSize.ToString()}"; 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /Installer.Core/FileSystem/DiskInfo.cs: -------------------------------------------------------------------------------- 1 | using ByteSizeLib; 2 | 3 | namespace Installer.Core.FileSystem 4 | { 5 | public class DiskInfo 6 | { 7 | public string FriendlyName { get; set; } 8 | public uint Number { get; set; } 9 | public ByteSize Size { get; set; } 10 | public ByteSize AllocatedSize { get; set; } 11 | public bool IsSystem { get; set; } 12 | public bool IsBoot { get; set; } 13 | public bool IsOffline { get; set; } 14 | public bool IsReadOnly { get; set; } 15 | } 16 | } -------------------------------------------------------------------------------- /Installer.Core/FileSystem/DriverMetadata.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Installer.Core.FileSystem 4 | { 5 | public class DriverMetadata 6 | { 7 | public string Driver { get; set; } 8 | public string OriginalFileName { get; set; } 9 | public bool Inbox { get; set; } 10 | public bool BootCritical { get; set; } 11 | public string ProviderName { get; set; } 12 | public DateTime Date { get; set; } 13 | public string Version { get; set; } 14 | } 15 | } -------------------------------------------------------------------------------- /Installer.Core/FileSystem/FileSystemFormat.cs: -------------------------------------------------------------------------------- 1 | namespace Installer.Core.FileSystem 2 | { 3 | public class FileSystemFormat 4 | { 5 | public string Moniker { get; } 6 | 7 | public static FileSystemFormat Ntfs = new FileSystemFormat("NTFS"); 8 | public static FileSystemFormat Fat16 = new FileSystemFormat("FAT"); 9 | public static FileSystemFormat Fat32 = new FileSystemFormat("FAT32"); 10 | 11 | private FileSystemFormat(string moniker) 12 | { 13 | Moniker = moniker; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /Installer.Core/FileSystem/FileSystemMixin.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace Installer.Core.FileSystem 4 | { 5 | public static class FileSystemMixin 6 | { 7 | public static string GetBcdFullFilename(this Volume self) 8 | { 9 | return Path.Combine(self.RootDir.Name, "EFI", "Microsoft", "Boot", "BCD"); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /Installer.Core/FileSystem/ILowLevelApi.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using ByteSizeLib; 4 | 5 | namespace Installer.Core.FileSystem 6 | { 7 | public interface ILowLevelApi 8 | { 9 | Task GetPhoneDisk(); 10 | Task ResizePartition(Partition partition, ByteSize sizeInBytes); 11 | Task> GetPartitions(Disk disk); 12 | Task GetVolume(Partition partition); 13 | Task CreateReservedPartition(Disk disk, ulong sizeInBytes); 14 | Task CreatePartition(Disk disk, ulong sizeInBytes); 15 | Task SetPartitionType(Partition partition, PartitionType partitionType); 16 | Task Format(Volume volume, FileSystemFormat ntfs, string fileSystemLabel); 17 | char GetFreeDriveLetter(); 18 | Task AssignDriveLetter(Volume volume, char letter); 19 | Task> GetVolumes(Disk disk); 20 | Task RemovePartition(Partition partition); 21 | Task> GetDisks(); 22 | Task> GetDrivers(string path); 23 | } 24 | } -------------------------------------------------------------------------------- /Installer.Core/FileSystem/Partition.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using ByteSizeLib; 3 | using Serilog; 4 | 5 | namespace Installer.Core.FileSystem 6 | { 7 | public class Partition 8 | { 9 | public Partition(Disk disk) 10 | { 11 | Disk = disk; 12 | } 13 | 14 | public Disk Disk { get; private set; } 15 | public uint Number { get; set; } 16 | public string Id { get; set; } 17 | public char? Letter { get; set; } 18 | public PartitionType PartitionType { get; set; } 19 | public ILowLevelApi LowLevelApi => Disk.LowLevelApi; 20 | 21 | public override string ToString() 22 | { 23 | return $"{nameof(Disk)}: {Disk}, {nameof(Number)}: {Number}"; 24 | } 25 | 26 | public async Task Resize(ByteSize sizeInBytes) 27 | { 28 | await LowLevelApi.ResizePartition(this, sizeInBytes); 29 | } 30 | 31 | public Task GetVolume() 32 | { 33 | return LowLevelApi.GetVolume(this); 34 | } 35 | 36 | public async Task SetGptType(PartitionType partitionType) 37 | { 38 | Log.Verbose("Setting partition type to {Partition} from {OldType} to {NewType}", this, PartitionType, partitionType); 39 | await LowLevelApi.SetPartitionType(this, partitionType); 40 | Log.Verbose("Partition type set"); 41 | } 42 | 43 | public Task Remove() 44 | { 45 | return LowLevelApi.RemovePartition(this); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /Installer.Core/FileSystem/PartitionType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Serilog; 4 | 5 | namespace Installer.Core.FileSystem 6 | { 7 | public class PartitionType 8 | { 9 | private static readonly Guid EspGuid = Guid.Parse("C12A7328-F81F-11D2-BA4B-00A0C93EC93B"); 10 | private static readonly Guid BasicGuid = Guid.Parse("EBD0A0A2-B9E5-4433-87C0-68B6B72699C7"); 11 | private static readonly Guid ReservedGuid = Guid.Parse("E3C9E316-0B5C-4DB8-817D-F92DF00215AE"); 12 | 13 | public string Name { get; } 14 | public Guid Guid { get; } 15 | 16 | public static readonly PartitionType Reserved = new PartitionType("Reserved", ReservedGuid); 17 | public static readonly PartitionType Esp = new PartitionType("EFI System Partition", EspGuid); 18 | public static readonly PartitionType Basic = new PartitionType("Basic", BasicGuid); 19 | 20 | private static readonly IDictionary PartitionTypes = new Dictionary() 21 | { 22 | { EspGuid, Esp}, 23 | { BasicGuid, Basic }, 24 | { ReservedGuid, Reserved }, 25 | }; 26 | 27 | private PartitionType(string name, Guid guid) 28 | { 29 | Name = name; 30 | Guid = guid; 31 | } 32 | 33 | public static PartitionType FromGuid(Guid guid) 34 | { 35 | if (PartitionTypes.TryGetValue(guid, out var type)) 36 | { 37 | return type; 38 | } 39 | 40 | //Log.Warning("The partition type {Guid} is unknown", guid); 41 | return null; 42 | } 43 | 44 | protected bool Equals(PartitionType other) 45 | { 46 | return Guid.Equals(other.Guid); 47 | } 48 | 49 | public override bool Equals(object obj) 50 | { 51 | if (ReferenceEquals(null, obj)) 52 | { 53 | return false; 54 | } 55 | 56 | if (ReferenceEquals(this, obj)) 57 | { 58 | return true; 59 | } 60 | 61 | if (obj.GetType() != this.GetType()) 62 | { 63 | return false; 64 | } 65 | 66 | return Equals((PartitionType) obj); 67 | } 68 | 69 | public override int GetHashCode() 70 | { 71 | return Guid.GetHashCode(); 72 | } 73 | 74 | public override string ToString() 75 | { 76 | return $"{nameof(Guid)}: {Guid}"; 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /Installer.Core/FileSystem/Volume.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Reactive; 5 | using System.Reactive.Linq; 6 | using System.Threading.Tasks; 7 | using ByteSizeLib; 8 | using Installer.Core.Utils; 9 | using Serilog; 10 | 11 | namespace Installer.Core.FileSystem 12 | { 13 | public class Volume 14 | { 15 | private DirectoryInfo rootDir; 16 | 17 | public Volume(Partition partition) 18 | { 19 | Partition = partition; 20 | } 21 | 22 | public string Label { get; set; } 23 | public ByteSize Size { get; set; } 24 | public Partition Partition { get; set; } 25 | public char? Letter { get; set; } 26 | 27 | public DirectoryInfo RootDir => rootDir ?? (rootDir = new DirectoryInfo($"{Letter}:")); 28 | 29 | public Task Format(FileSystemFormat ntfs, string fileSystemLabel) 30 | { 31 | return Partition.LowLevelApi.Format(this, ntfs, fileSystemLabel); 32 | } 33 | 34 | public ILowLevelApi LowLevelApi => Partition.LowLevelApi; 35 | 36 | public async Task Mount() 37 | { 38 | Log.Verbose("Mounting volume {Volume}", this); 39 | var driveLetter = LowLevelApi.GetFreeDriveLetter(); 40 | await LowLevelApi.AssignDriveLetter(this, driveLetter); 41 | 42 | await Observable.Defer(() => Observable.Return(UpdateLetter(driveLetter))).RetryWithBackoffStrategy(); 43 | } 44 | 45 | private Unit UpdateLetter(char driveLetter) 46 | { 47 | try 48 | { 49 | rootDir = new DirectoryInfo($"{driveLetter}:"); 50 | return Unit.Default; 51 | } 52 | catch (Exception) 53 | { 54 | Log.Verbose("Cannot get path for drive letter {DriveLetter} while mounting partition {Partition}", driveLetter, this); 55 | throw; 56 | } 57 | } 58 | 59 | public override string ToString() 60 | { 61 | return $"{nameof(Label)}: {Label}, {nameof(Size)}: {Size}, {nameof(Partition)}: {Partition}, {nameof(Letter)}: {Letter}"; 62 | } 63 | 64 | public Task> GetDrivers() 65 | { 66 | if (Partition.Letter == null) 67 | { 68 | throw new InvalidOperationException("The partition doesn't have a drive letter"); 69 | } 70 | 71 | return LowLevelApi.GetDrivers(Partition.Letter + ":\\"); 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /Installer.Core/ICoreDeployer.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace Installer.Core 4 | { 5 | public interface ICoreDeployer where TDevice : Device 6 | { 7 | Task AreDeploymentFilesValid(); 8 | Task Deploy(TDevice phone); 9 | } 10 | } -------------------------------------------------------------------------------- /Installer.Core/IDeployer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace Installer.Core 5 | { 6 | public interface IDeployer where TDevice : Device 7 | { 8 | Task DeployCoreAndWindows(InstallOptions options, TDevice device, IObserver progressObserver = null); 9 | Task DeployWindows(InstallOptions options, TDevice device, IObserver progressObserver = null); 10 | Task InjectPostOobeDrivers(TDevice device); 11 | } 12 | } -------------------------------------------------------------------------------- /Installer.Core/InstallOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ByteSizeLib; 3 | 4 | namespace Installer.Core 5 | { 6 | public class InstallOptions 7 | { 8 | public InstallOptions(string imagePath) 9 | { 10 | ImagePath = imagePath; 11 | } 12 | 13 | public string ImagePath { get; } 14 | public int ImageIndex { get; set; } = 1; 15 | public bool PatchBoot { get; set; } 16 | public ByteSize SizeReservedForWindows { get; set; } = ByteSize.FromGigaBytes(18); 17 | } 18 | } -------------------------------------------------------------------------------- /Installer.Core/Installer.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | True 21 | True 22 | Resources.resx 23 | 24 | 25 | 26 | 27 | 28 | PublicResXFileCodeGenerator 29 | Resources.Designer.cs 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /Installer.Core/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Installer.Core { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | public class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | public static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Installer.Core.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | public static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized string similar to Windows 10 ARM64 isn't fully installed. 65 | /// 66 | ///In order to inject these drivers, you must complete Windows Setup until the Desktop screen is shown. 67 | /// 68 | ///Please, complete Windows Setup first.. 69 | /// 70 | public static string DriversInjectionWindowsNotFullyInstalled { 71 | get { 72 | return ResourceManager.GetString("DriversInjectionWindowsNotFullyInstalled", resourceCulture); 73 | } 74 | } 75 | 76 | /// 77 | /// Looks up a localized string similar to Could not find a valid "Files" repository. 78 | ///Please, download a fresh copy and put it inside a folder name "Files" inside the root folder of this tool.. 79 | /// 80 | public static string EnsureValidFilesRepository { 81 | get { 82 | return ResourceManager.GetString("EnsureValidFilesRepository", resourceCulture); 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Installer.Core/ServiceFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Installer.Core.Services; 3 | 4 | namespace Installer.Core 5 | { 6 | public abstract class ServiceFactory 7 | { 8 | private static ServiceFactory current; 9 | public IWindowsImageService ImageService { get; protected set; } 10 | public DiskService DiskService { get; protected set; } 11 | 12 | public static ServiceFactory Current 13 | { 14 | get 15 | { 16 | if (current == null) 17 | { 18 | throw new InvalidOperationException("Please, set the Current property of the ServiceFactory"); 19 | } 20 | 21 | return current; 22 | } 23 | set => current = value; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /Installer.Core/Services/ArchiveUncompressor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Reactive.Linq; 6 | using System.Threading.Tasks; 7 | using Installer.Core.Utils; 8 | using Serilog; 9 | using SharpCompress.Archives; 10 | using SharpCompress.Common; 11 | using ExtractionException = Installer.Core.Exceptions.ExtractionException; 12 | 13 | namespace Installer.Core.Services 14 | { 15 | public class ArchiveUncompressor : IArchiveUncompressor 16 | where TEntry : IArchiveEntry 17 | where TVolume : IVolume 18 | where TArchive : AbstractArchive 19 | { 20 | private readonly Func getArchive; 21 | 22 | public ArchiveUncompressor(Func getArchive) 23 | { 24 | this.getArchive = getArchive; 25 | } 26 | 27 | public async Task Extract(string archivePath, string destination, IObserver progressObserver = null) 28 | { 29 | Log.Information("Extracting from {File}", archivePath); 30 | 31 | using (var package = getArchive(archivePath)) 32 | { 33 | var entries = package.Entries.Where(x => !x.IsDirectory).ToList(); 34 | 35 | FileUtils.DeleteDirectyRecursive(destination); 36 | CreateDestinationDirectories(entries, destination); 37 | await Extract(entries.ToList(), destination, progressObserver); 38 | } 39 | 40 | Log.Information("Extraction successful from {Path}", archivePath); 41 | } 42 | 43 | public async Task ReadToEnd(string archivePath, string key) 44 | { 45 | Log.Information("Importing Core Package from {File}", archivePath); 46 | 47 | using (var package = getArchive(archivePath)) 48 | { 49 | var entry = package.Entries.SingleOrDefault(x => !x.IsDirectory && x.Key.StartsWith(key, StringComparison.InvariantCultureIgnoreCase)); 50 | if (entry == null) 51 | { 52 | return null; 53 | } 54 | 55 | using (var reader = new StreamReader(entry.OpenEntryStream())) 56 | { 57 | var text = await reader.ReadToEndAsync(); 58 | Log.Information("Entry read"); 59 | return text; 60 | } 61 | } 62 | } 63 | 64 | private void CreateDestinationDirectories(IEnumerable entries, string destination) 65 | { 66 | var directories = entries.Select(x => Path.GetDirectoryName(x.Key.Replace("/", "\\"))).Distinct(); 67 | foreach (var path in directories) 68 | { 69 | Directory.CreateDirectory(Path.Combine(destination, path)); 70 | } 71 | } 72 | 73 | 74 | private static async Task Extract(ICollection entries, string destination, IObserver progressObserver = null) 75 | { 76 | var count = entries.Count; 77 | double i = 0; 78 | 79 | var obs = entries 80 | .ToObservable() 81 | .Select(x => Observable.FromAsync(() => ExtractEntryToFile(x, destination))) 82 | .Merge(1) 83 | .Publish(); 84 | 85 | var updater = obs.Subscribe(_ => 86 | { 87 | progressObserver?.OnNext(i / count); 88 | i++; 89 | }); 90 | 91 | obs.Connect(); 92 | await obs.ToList(); 93 | 94 | updater.Dispose(); 95 | 96 | progressObserver?.OnNext(double.NaN); 97 | } 98 | 99 | private static async Task ExtractEntryToFile(TEntry pe, string destination) 100 | { 101 | using (var input = pe.OpenEntryStream()) 102 | { 103 | var replacedPath = pe.Key.Replace("/", "\\"); 104 | var finalPath = Path.Combine(destination, replacedPath); 105 | using (var output = File.OpenWrite(finalPath)) 106 | { 107 | await input.CopyToAsync(output); 108 | } 109 | } 110 | } 111 | } 112 | } -------------------------------------------------------------------------------- /Installer.Core/Services/BcdConfigurator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Installer.Core.FileSystem; 3 | using Installer.Core.Utils; 4 | 5 | namespace Installer.Core.Services 6 | { 7 | public class BcdConfigurator 8 | { 9 | private readonly BcdInvoker invoker; 10 | private readonly Volume efiespVolume; 11 | 12 | public BcdConfigurator(BcdInvoker invoker, Volume efiespVolume) 13 | { 14 | this.invoker = invoker; 15 | this.efiespVolume = efiespVolume; 16 | } 17 | 18 | public void SetupBcd() 19 | { 20 | var bootShimEntry = CreateBootShim(); 21 | SetupBootShim(bootShimEntry); 22 | SetupBootMgr(); 23 | SetDisplayOptions(bootShimEntry); 24 | } 25 | 26 | private void SetDisplayOptions(Guid entry) 27 | { 28 | invoker.Invoke($@"/displayorder {{{entry}}}"); 29 | invoker.Invoke($@"/default {{{entry}}}"); 30 | invoker.Invoke($@"/timeout 30"); 31 | } 32 | 33 | private void SetupBootShim(Guid guid) 34 | { 35 | invoker.Invoke($@"/set {{{guid}}} path \EFI\boot\BootShim.efi"); 36 | invoker.Invoke($@"/set {{{guid}}} device partition={efiespVolume.RootDir.Name}"); 37 | invoker.Invoke($@"/set {{{guid}}} testsigning on"); 38 | invoker.Invoke($@"/set {{{guid}}} nointegritychecks on"); 39 | } 40 | 41 | private void SetupBootMgr() 42 | { 43 | invoker.Invoke($@"/set {{bootmgr}} displaybootmenu on"); 44 | invoker.Invoke($@"/deletevalue {{bootmgr}} customactions"); 45 | invoker.Invoke($@"/deletevalue {{bootmgr}} custom:54000001"); 46 | invoker.Invoke($@"/deletevalue {{bootmgr}} custom:54000002"); 47 | invoker.Invoke($@"/deletevalue {{bootmgr}} processcustomactionsfirst"); 48 | } 49 | 50 | private Guid CreateBootShim() 51 | { 52 | var invokeText = invoker.Invoke(@"/create /d ""Windows 10"" /application BOOTAPP"); 53 | return FormattingUtils.GetGuid(invokeText); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /Installer.Core/Services/BcdInvoker.cs: -------------------------------------------------------------------------------- 1 | using Installer.Core.Utils; 2 | 3 | namespace Installer.Core.Services 4 | { 5 | public class BcdInvoker 6 | { 7 | private readonly string commonArgs; 8 | private readonly string bcdEdit; 9 | 10 | public BcdInvoker(string store) 11 | { 12 | bcdEdit = SystemPaths.BcdEdit; 13 | commonArgs = $@"/STORE ""{store}"""; 14 | } 15 | 16 | public string Invoke(string command) 17 | { 18 | return ProcessUtils.Run(bcdEdit, $@"{commonArgs} {command}"); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /Installer.Core/Services/DeploymentPaths.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace Installer.Core.Services 4 | { 5 | public class DeploymentPaths 6 | { 7 | private readonly string rootPath; 8 | 9 | public DeploymentPaths(string rootPath) 10 | { 11 | this.rootPath = rootPath; 12 | } 13 | 14 | public string PreOobe => Path.Combine(rootPath, "Drivers", "Pre-OOBE"); 15 | public string PostOobe => Path.Combine(rootPath, "Drivers", "Post-OOBE"); 16 | public string BootPatchFolder => Path.Combine(rootPath, "Patch"); 17 | } 18 | } -------------------------------------------------------------------------------- /Installer.Core/Services/DiskService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using Installer.Core.FileSystem; 4 | 5 | namespace Installer.Core.Services 6 | { 7 | public class DiskService 8 | { 9 | private readonly ILowLevelApi api; 10 | 11 | public DiskService(ILowLevelApi api) 12 | { 13 | this.api = api; 14 | } 15 | 16 | public Task> GetDisks() 17 | { 18 | return api.GetDisks(); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /Installer.Core/Services/IArchiveUncompressor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace Installer.Core.Services 5 | { 6 | public interface IArchiveUncompressor 7 | { 8 | Task Extract(string archivePath, string destination, IObserver progressObserver = null); 9 | Task ReadToEnd(string archivePath, string key); 10 | } 11 | } -------------------------------------------------------------------------------- /Installer.Core/Services/IImageFlasher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Installer.Core.FileSystem; 4 | 5 | namespace Installer.Core.Services 6 | { 7 | public interface IImageFlasher 8 | { 9 | Task Flash(Disk disk, string imagePath, IObserver progressObserver = null); 10 | } 11 | } -------------------------------------------------------------------------------- /Installer.Core/Services/IPackageImporter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace Installer.Core.Services 5 | { 6 | public interface IPackageImporter 7 | { 8 | Task Extract(string packagePath, IObserver progressObserver = null); 9 | Task GetReadmeText(string fileName); 10 | } 11 | } -------------------------------------------------------------------------------- /Installer.Core/Services/IWindowsDeployer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace Installer.Core.Services 5 | { 6 | public interface IWindowsDeployer where TDevice : Device 7 | { 8 | Task Deploy(InstallOptions options, TDevice device, IObserver progressObserver = null); 9 | Task InjectPostOobeDrivers(TDevice phone); 10 | Task AreDeploymentFilesValid(); 11 | } 12 | } -------------------------------------------------------------------------------- /Installer.Core/Services/IWindowsImageService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Installer.Core.FileSystem; 4 | 5 | namespace Installer.Core.Services 6 | { 7 | public interface IWindowsImageService 8 | { 9 | Task ApplyImage(Volume windowsVolume, string imagePath, int imageIndex = 1, IObserver progressObserver = null); 10 | Task InjectDrivers(string path, Volume windowsPartition); 11 | Task RemoveDriver(string path, Volume volume); 12 | } 13 | } -------------------------------------------------------------------------------- /Installer.Core/Services/Packages/PackageImporter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace Installer.Core.Services.Packages 5 | { 6 | public class PackageImporter : IPackageImporter 7 | { 8 | private readonly IArchiveUncompressor uncompressor; 9 | 10 | public PackageImporter(IArchiveUncompressor uncompressor) 11 | { 12 | this.uncompressor = uncompressor; 13 | } 14 | 15 | public Task Extract(string packagePath, IObserver progressObserver = null) 16 | { 17 | return uncompressor.Extract(packagePath, "Files", progressObserver); 18 | } 19 | 20 | public Task GetReadmeText(string fileName) 21 | { 22 | return uncompressor.ReadToEnd(fileName, "Readme.txt"); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Installer.Core/Services/SystemPaths.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace Installer.Core.Services 5 | { 6 | public static class SystemPaths 7 | { 8 | public static string BcdEdit { get; } = Path.Combine(GetSystemFolder, "bcdedit.exe"); 9 | public static string BcdBoot { get; } = Path.Combine(GetSystemFolder, "bcdboot.exe"); 10 | public static string Dism { get; } = Path.Combine(GetSystemFolder, "dism.exe"); 11 | 12 | private static string GetSystemFolder 13 | { 14 | get 15 | { 16 | var shouldUseSysNative = Environment.Is64BitOperatingSystem && !Environment.Is64BitProcess; 17 | 18 | if (shouldUseSysNative) 19 | { 20 | return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "SysNative"); 21 | } 22 | 23 | return Path.Combine(Environment.SystemDirectory); 24 | } 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /Installer.Core/Services/Wim/DiskImageMetadata.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace Installer.Core.Services.Wim 4 | { 5 | public class DiskImageMetadata 6 | { 7 | public int Index { get; set; } 8 | public string DisplayName { get; set; } 9 | public Architecture Architecture { get; set; } 10 | public string Build { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /Installer.Core/Services/Wim/IWindowsImageMetadataReader.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace Installer.Core.Services.Wim 4 | { 5 | public interface IWindowsImageMetadataReader 6 | { 7 | XmlWindowsImageMetadata Load(Stream stream); 8 | } 9 | } -------------------------------------------------------------------------------- /Installer.Core/Services/Wim/ImageMetadata.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Serialization; 2 | 3 | namespace Installer.Core.Services.Wim 4 | { 5 | [XmlRoot(ElementName = "IMAGE")] 6 | public class ImageMetadata 7 | { 8 | [XmlElement(ElementName = "DIRCOUNT")] public string DirectoryCount { get; set; } 9 | 10 | [XmlElement(ElementName = "FILECOUNT")] 11 | public string FileCount { get; set; } 12 | 13 | [XmlElement(ElementName = "TOTALBYTES")] 14 | public string TotalBytes { get; set; } 15 | 16 | [XmlElement(ElementName = "HARDLINKBYTES")] 17 | public string HardLinkBytes { get; set; } 18 | 19 | [XmlElement(ElementName = "CREATIONTIME")] 20 | public Time CreationTime { get; set; } 21 | 22 | [XmlElement(ElementName = "LASTMODIFICATIONTIME")] 23 | public Time LastModificationTime { get; set; } 24 | 25 | [XmlElement(ElementName = "WIMBOOT")] public string WimBoot { get; set; } 26 | 27 | [XmlElement(ElementName = "WINDOWS")] public Windows Windows { get; set; } 28 | 29 | [XmlElement(ElementName = "NAME")] public string Name { get; set; } 30 | 31 | [XmlElement(ElementName = "DESCRIPTION")] 32 | public string Description { get; set; } 33 | 34 | [XmlElement(ElementName = "FLAGS")] public string Flags { get; set; } 35 | 36 | [XmlElement(ElementName = "DISPLAYNAME")] 37 | public string DiplayName { get; set; } 38 | 39 | [XmlElement(ElementName = "DISPLAYDESCRIPTION")] 40 | public string DisplayDescription { get; set; } 41 | 42 | [XmlAttribute(AttributeName = "INDEX")] 43 | public string Index { get; set; } 44 | } 45 | } -------------------------------------------------------------------------------- /Installer.Core/Services/Wim/Languages.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Serialization; 2 | 3 | namespace Installer.Core.Services.Wim 4 | { 5 | [XmlRoot(ElementName = "LANGUAGES")] 6 | public class Languages 7 | { 8 | [XmlElement(ElementName = "LANGUAGE")] public string Language { get; set; } 9 | 10 | [XmlElement(ElementName = "DEFAULT")] public string Default { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /Installer.Core/Services/Wim/ServicingData.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Serialization; 2 | 3 | namespace Installer.Core.Services.Wim 4 | { 5 | [XmlRoot(ElementName = "SERVICINGDATA")] 6 | public class ServicingData 7 | { 8 | [XmlElement(ElementName = "GDRDUREVISION")] 9 | public string GdrDuRevision { get; set; } 10 | 11 | [XmlElement(ElementName = "PKEYCONFIGVERSION")] 12 | public string PKeyConfigVersion { get; set; } 13 | } 14 | } -------------------------------------------------------------------------------- /Installer.Core/Services/Wim/Time.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Serialization; 2 | 3 | namespace Installer.Core.Services.Wim 4 | { 5 | [XmlRoot(ElementName = "CREATIONTIME")] 6 | public class Time 7 | { 8 | [XmlElement(ElementName = "HIGHPART")] public string HighPart { get; set; } 9 | 10 | [XmlElement(ElementName = "LOWPART")] public string LowPart { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /Installer.Core/Services/Wim/Version.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Serialization; 2 | 3 | namespace Installer.Core.Services.Wim 4 | { 5 | [XmlRoot(ElementName = "VERSION")] 6 | public class Version 7 | { 8 | [XmlElement(ElementName = "MAJOR")] public string Major { get; set; } 9 | 10 | [XmlElement(ElementName = "MINOR")] public string Minor { get; set; } 11 | 12 | [XmlElement(ElementName = "BUILD")] public string Build { get; set; } 13 | 14 | [XmlElement(ElementName = "SPBUILD")] public string SpBuild { get; set; } 15 | 16 | [XmlElement(ElementName = "SPLEVEL")] public string SpLevel { get; set; } 17 | } 18 | } -------------------------------------------------------------------------------- /Installer.Core/Services/Wim/WimMetadata.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Xml.Serialization; 3 | 4 | namespace Installer.Core.Services.Wim 5 | { 6 | [XmlRoot(ElementName = "WIM")] 7 | public class WimMetadata 8 | { 9 | [XmlElement(ElementName = "TOTALBYTES")] 10 | public string TotalBytes { get; set; } 11 | 12 | [XmlElement(ElementName = "IMAGE")] public List Images { get; set; } 13 | } 14 | } -------------------------------------------------------------------------------- /Installer.Core/Services/Wim/Windows.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Serialization; 2 | 3 | namespace Installer.Core.Services.Wim 4 | { 5 | [XmlRoot(ElementName = "WINDOWS")] 6 | public class Windows 7 | { 8 | [XmlElement(ElementName = "ARCH")] public string Arch { get; set; } 9 | 10 | [XmlElement(ElementName = "PRODUCTNAME")] 11 | public string ProductName { get; set; } 12 | 13 | [XmlElement(ElementName = "EDITIONID")] 14 | public string EditionId { get; set; } 15 | 16 | [XmlElement(ElementName = "INSTALLATIONTYPE")] 17 | public string InstallationType { get; set; } 18 | 19 | [XmlElement(ElementName = "SERVICINGDATA")] 20 | public ServicingData ServicingData { get; set; } 21 | 22 | [XmlElement(ElementName = "PRODUCTTYPE")] 23 | public string ProductType { get; set; } 24 | 25 | [XmlElement(ElementName = "PRODUCTSUITE")] 26 | public string ProductSuite { get; set; } 27 | 28 | [XmlElement(ElementName = "LANGUAGES")] 29 | public Languages Languages { get; set; } 30 | 31 | [XmlElement(ElementName = "VERSION")] public Version Version { get; set; } 32 | 33 | [XmlElement(ElementName = "SYSTEMROOT")] 34 | public string SystemRoot { get; set; } 35 | } 36 | } -------------------------------------------------------------------------------- /Installer.Core/Services/Wim/WindowsImageMetadataReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using Serilog; 5 | 6 | namespace Installer.Core.Services.Wim 7 | { 8 | public class WindowsImageMetadataReader : WindowsImageMetadataReaderBase 9 | { 10 | private static long ToInt64LittleEndian(byte[] buffer, int offset) 11 | { 12 | return (long)ToUInt64LittleEndian(buffer, offset); 13 | } 14 | 15 | private static uint ToUInt32LittleEndian(byte[] buffer, int offset) 16 | { 17 | var a = (buffer[offset + 3] << 24) & 0xFF000000U; 18 | var b = (buffer[offset + 2] << 16) & 0x00FF0000U; 19 | var c = (buffer[offset + 1] << 8) & 0x0000FF00U; 20 | var d = (buffer[offset + 0] << 0) & 0x000000FFU; 21 | 22 | return (uint)(a | b | c | d); 23 | } 24 | 25 | 26 | private static ulong ToUInt64LittleEndian(byte[] buffer, int offset) 27 | { 28 | return ((ulong)ToUInt32LittleEndian(buffer, offset + 4) << 32) | ToUInt32LittleEndian(buffer, offset + 0); 29 | } 30 | 31 | // 32 | // https://stackoverflow.com/questions/1471975/best-way-to-find-position-in-the-stream-where-given-byte-sequence-starts 33 | // 34 | public static long FindPosition(Stream stream, byte[] byteSequence) 35 | { 36 | if (byteSequence.Length > stream.Length) 37 | { 38 | return -1; 39 | } 40 | 41 | var buffer = new byte[byteSequence.Length]; 42 | 43 | var bufStream = new BufferedStream(stream, byteSequence.Length); 44 | int i; 45 | 46 | while ((i = bufStream.Read(buffer, 0, byteSequence.Length)) == byteSequence.Length) 47 | { 48 | if (byteSequence.SequenceEqual(buffer)) 49 | { 50 | return bufStream.Position - byteSequence.Length; 51 | } 52 | 53 | bufStream.Position -= byteSequence.Length - PadLeftSequence(buffer, byteSequence); 54 | } 55 | 56 | return -1; 57 | } 58 | 59 | private static int PadLeftSequence(byte[] bytes, byte[] seqBytes) 60 | { 61 | var i = 1; 62 | while (i < bytes.Length) 63 | { 64 | var n = bytes.Length - i; 65 | var aux1 = new byte[n]; 66 | var aux2 = new byte[n]; 67 | Array.Copy(bytes, i, aux1, 0, n); 68 | Array.Copy(seqBytes, aux2, n); 69 | if (aux1.SequenceEqual(aux2)) 70 | { 71 | return i; 72 | } 73 | 74 | i++; 75 | } 76 | 77 | return i; 78 | } 79 | 80 | protected override Stream GetXmlMetadataStream(Stream wim) 81 | { 82 | var outputstream = new MemoryStream(); 83 | var wimwriter = new BinaryWriter(outputstream); 84 | using (var wimsecstream = wim) 85 | { 86 | using (var wimsecreader = new BinaryReader(wimsecstream)) 87 | { 88 | var bytes = new byte[] 89 | { 90 | 0x4D, 0x53, 0x57, 0x49, 0x4D 91 | }; 92 | 93 | Log.Verbose("(WIM) Finding Magic Bytes..."); 94 | 95 | var start = WindowsImageMetadataReader.FindPosition(wimsecstream, bytes); 96 | 97 | Log.Verbose("(WIM) Found Magic Bytes at " + start); 98 | 99 | Log.Verbose("(WIM) Finding WIM XML Data..."); 100 | 101 | var endbytes = new byte[] 102 | { 103 | 0x3C, 0x00, 0x2F, 0x00, 0x57, 0x00, 0x49, 0x00, 0x4D, 0x00, 0x3E, 0x00 104 | }; 105 | 106 | wimsecstream.Seek(start + 72, SeekOrigin.Begin); 107 | var buffer = new byte[24]; 108 | wimsecstream.Read(buffer, 0, 24); 109 | var may = WindowsImageMetadataReader.ToInt64LittleEndian(buffer, 8); 110 | wimsecstream.Seek(start, SeekOrigin.Begin); 111 | 112 | Log.Verbose("(WIM) Found WIM XML Data at " + start + may + 2); 113 | 114 | wimsecstream.Seek(start + may + 2, SeekOrigin.Begin); 115 | 116 | for (var i = wimsecstream.Position; i < wimsecstream.Length - endbytes.Length; i++) 117 | { 118 | if (BitConverter.ToString(wimsecreader.ReadBytes(12)) == BitConverter.ToString(endbytes)) 119 | { 120 | wimwriter.Write(endbytes); 121 | break; 122 | } 123 | 124 | wimsecstream.Seek(-12, SeekOrigin.Current); 125 | wimwriter.Write(wimsecreader.ReadBytes(1)); 126 | } 127 | } 128 | } 129 | 130 | outputstream.Seek(0, SeekOrigin.Begin); 131 | return outputstream; 132 | } 133 | } 134 | } -------------------------------------------------------------------------------- /Installer.Core/Services/Wim/WindowsImageMetadataReaderBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Runtime.InteropServices; 5 | using System.Xml.Serialization; 6 | using Installer.Core.Exceptions; 7 | using Serilog; 8 | 9 | namespace Installer.Core.Services.Wim 10 | { 11 | public abstract class WindowsImageMetadataReaderBase : IWindowsImageMetadataReader 12 | { 13 | private static XmlSerializer Serializer { get; } = new XmlSerializer(typeof(WimMetadata)); 14 | 15 | public XmlWindowsImageMetadata Load(Stream stream) 16 | { 17 | Log.Verbose("Getting WIM stream"); 18 | 19 | WimMetadata metadata; 20 | try 21 | { 22 | metadata = (WimMetadata)Serializer.Deserialize(GetXmlMetadataStream(stream)); 23 | } 24 | catch (InvalidOperationException e) 25 | { 26 | throw new InvalidWimFileException("Could not read the metadata from the WIM file. Please, check it's a valid .WIM file", e); 27 | } 28 | 29 | Log.Verbose("Wim metadata deserialized correctly {@Metadata}", metadata); 30 | 31 | return new XmlWindowsImageMetadata 32 | { 33 | Images = metadata.Images 34 | .Where(x => x.Windows != null) 35 | .Select(x => new DiskImageMetadata 36 | { 37 | Architecture = GetArchitecture(x.Windows.Arch), 38 | Build = x.Windows.Version.Build, 39 | DisplayName = x.Name, 40 | Index = int.Parse(x.Index) 41 | }).ToList() 42 | }; 43 | } 44 | 45 | private static Architecture GetArchitecture(string str) 46 | { 47 | switch (str) 48 | { 49 | case "0": 50 | return Architecture.X86; 51 | case "9": 52 | return Architecture.X64; 53 | case "12": 54 | return Architecture.Arm64; 55 | } 56 | 57 | throw new IndexOutOfRangeException($"The architecture '{str}' is unknown"); 58 | } 59 | 60 | protected abstract Stream GetXmlMetadataStream(Stream wim); 61 | } 62 | } -------------------------------------------------------------------------------- /Installer.Core/Services/Wim/XmlWindowsImageMetadata.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Installer.Core.Services.Wim 4 | { 5 | public class XmlWindowsImageMetadata 6 | { 7 | public IList Images { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /Installer.Core/Services/Wim/XmlWindowsImageMetadataReader.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace Installer.Core.Services.Wim 4 | { 5 | public class XmlWindowsImageMetadataReader : WindowsImageMetadataReaderBase 6 | { 7 | protected override Stream GetXmlMetadataStream(Stream wim) 8 | { 9 | return wim; 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /Installer.Core/Services/WindowsVolumes.cs: -------------------------------------------------------------------------------- 1 | using Installer.Core.FileSystem; 2 | 3 | namespace Installer.Core.Services 4 | { 5 | public class WindowsVolumes 6 | { 7 | public WindowsVolumes(Volume bootVolume, Volume windowsVolume) 8 | { 9 | Boot = bootVolume; 10 | Windows = windowsVolume; 11 | } 12 | 13 | public Volume Boot { get; } 14 | public Volume Windows { get; } 15 | } 16 | } -------------------------------------------------------------------------------- /Installer.Core/Utils/FileUtils.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Linq; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Serilog; 6 | 7 | namespace Installer.Core.Utils 8 | { 9 | public static class FileUtils 10 | { 11 | public static bool EnsureExistingPaths(this string[] pathsToCheck) 12 | { 13 | return pathsToCheck.All(IsExistingPath); 14 | } 15 | 16 | public static void DeleteDirectyRecursive(string path) 17 | { 18 | Log.Verbose("Ensuring that '{Directory}' is empty", path); 19 | 20 | if (Directory.Exists(path)) 21 | { 22 | Directory.Delete(path, true); 23 | } 24 | } 25 | 26 | public static async Task Copy(string source, string destination, CancellationToken cancellationToken) 27 | { 28 | var fileOptions = FileOptions.Asynchronous | FileOptions.SequentialScan; 29 | var bufferSize = 4096; 30 | 31 | using (var sourceStream = 32 | new FileStream(source, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, fileOptions)) 33 | 34 | using (var destinationStream = 35 | new FileStream(destination, FileMode.CreateNew, FileAccess.Write, FileShare.None, bufferSize, fileOptions)) 36 | 37 | await sourceStream.CopyToAsync(destinationStream, bufferSize, cancellationToken).ConfigureAwait(false); 38 | } 39 | 40 | public static async Task Copy(string source, string destination, FileMode fileMode = FileMode.Create) 41 | { 42 | Log.Verbose("Copying file {Source} to {Destination}", source, destination); 43 | 44 | var fileOptions = FileOptions.Asynchronous | FileOptions.SequentialScan; 45 | var bufferSize = 4096; 46 | 47 | using (var sourceStream = 48 | new FileStream(source, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, fileOptions)) 49 | 50 | using (var destinationStream = 51 | new FileStream(destination, fileMode, FileAccess.Write, FileShare.None, bufferSize, fileOptions)) 52 | 53 | await sourceStream.CopyToAsync(destinationStream, bufferSize).ConfigureAwait(false); 54 | } 55 | 56 | public static async Task CopyDirectory(DirectoryInfo source, DirectoryInfo destination) 57 | { 58 | Log.Verbose("Copying directory {Source} to {Destination}", source, destination); 59 | 60 | 61 | foreach (var dir in source.GetDirectories()) 62 | { 63 | await CopyDirectory(dir, destination.CreateSubdirectory(dir.Name)); 64 | } 65 | 66 | foreach (var file in source.GetFiles()) 67 | { 68 | var destFileName = Path.Combine(destination.FullName, file.Name); 69 | await Copy(file.FullName, destFileName); 70 | } 71 | } 72 | 73 | private static bool IsExistingPath(string path) 74 | { 75 | var isExistingPath = File.Exists(path) || Directory.Exists(path); 76 | var status = isExistingPath ? "exists" : "does not exist"; 77 | 78 | Log.Verbose($"The '{{Path}}' {status}", path); 79 | 80 | return isExistingPath; 81 | } 82 | 83 | public static void CreateDirectory(string destPath) 84 | { 85 | if (!IsExistingPath(destPath)) 86 | { 87 | Directory.CreateDirectory(destPath); 88 | } 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /Installer.Core/Utils/FormattingUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text.RegularExpressions; 4 | 5 | namespace Installer.Core.Utils 6 | { 7 | public static class FormattingUtils 8 | { 9 | public static Guid GetGuid(string str) 10 | { 11 | var guids = Regex.Matches(str, @"(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}"); 12 | var match = guids.Cast().First(x => x.Success).Value; 13 | return Guid.Parse(match); 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /Installer.Core/Utils/ProcessUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Threading.Tasks; 5 | using Serilog; 6 | 7 | namespace Installer.Core.Utils 8 | { 9 | public static class ProcessUtils 10 | { 11 | public static string Run(string command, string arguments) 12 | { 13 | var process = new Process 14 | { 15 | StartInfo = 16 | { 17 | FileName = command, 18 | Arguments = arguments, 19 | UseShellExecute = false, 20 | RedirectStandardOutput = true, 21 | RedirectStandardError = true, 22 | CreateNoWindow = true, 23 | } 24 | }; 25 | 26 | Log.Verbose("Starting process {@Process}", new { process.StartInfo.FileName, process.StartInfo.Arguments }); 27 | process.Start(); 28 | Log.Verbose("Process started successfully"); 29 | 30 | 31 | string output = process.StandardOutput.ReadToEnd(); 32 | Console.WriteLine(output); 33 | string err = process.StandardError.ReadToEnd(); 34 | Console.WriteLine(err); 35 | process.WaitForExit(); 36 | 37 | Log.Verbose("Process output {Output}", output); 38 | 39 | return output; 40 | } 41 | 42 | 43 | public static async Task RunProcessAsync(string fileName, string args = "", IObserver outputObserver = null, IObserver errorObserver = null) 44 | { 45 | using (var process = new Process 46 | { 47 | StartInfo = 48 | { 49 | FileName = fileName, 50 | Arguments = args, 51 | UseShellExecute = false, 52 | CreateNoWindow = true, 53 | RedirectStandardOutput = true, 54 | RedirectStandardError = true 55 | }, 56 | EnableRaisingEvents = true 57 | }) 58 | { 59 | return await RunProcessAsync(process, outputObserver, errorObserver).ConfigureAwait(false); 60 | } 61 | } 62 | 63 | private static Task RunProcessAsync(Process process, IObserver outputObserver, IObserver errorObserver) 64 | { 65 | var tcs = new TaskCompletionSource(); 66 | 67 | process.Exited += (s, ea) => tcs.SetResult(process.ExitCode); 68 | 69 | if (outputObserver != null) 70 | { 71 | process.OutputDataReceived += (s, ea) => outputObserver.OnNext(ea.Data); 72 | } 73 | 74 | if (errorObserver != null) 75 | { 76 | process.ErrorDataReceived += (s, ea) => errorObserver?.OnNext(ea.Data); 77 | } 78 | 79 | Log.Verbose("Starting process {@Process}", new { process.StartInfo.FileName, process.StartInfo.Arguments }); 80 | bool started = process.Start(); 81 | Log.Verbose("Process started sucessfully"); 82 | 83 | if (!started) 84 | { 85 | //you may allow for the process to be re-used (started = false) 86 | //but I'm not sure about the guarantees of the Exited event in such a case 87 | throw new InvalidOperationException("Could not start process: " + process); 88 | } 89 | 90 | process.BeginOutputReadLine(); 91 | process.BeginErrorReadLine(); 92 | 93 | return tcs.Task; 94 | } 95 | } 96 | } -------------------------------------------------------------------------------- /Installer.Core/Utils/ReactiveMixin.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.Reactive.Concurrency; 4 | using System.Reactive.Linq; 5 | 6 | namespace Installer.Core.Utils 7 | { 8 | public static class ReactiveMixin 9 | { 10 | // Licensed under the MIT license with <3 by GitHub 11 | 12 | /// 13 | /// An exponential back off strategy which starts with 1 second and then 4, 9, 16... 14 | /// 15 | [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] 16 | public static readonly Func ExponentialBackoff = n => TimeSpan.FromSeconds(Math.Pow(n, 2)); 17 | 18 | /// 19 | /// Returns a cold observable which retries (re-subscribes to) the source observable on error up to the 20 | /// specified number of times or until it successfully terminates. Allows for customizable back off strategy. 21 | /// 22 | /// The source observable. 23 | /// The number of attempts of running the source observable before failing. 24 | /// The strategy to use in backing off, exponential by default. 25 | /// A predicate determining for which exceptions to retry. Defaults to all 26 | /// The scheduler. 27 | /// 28 | /// A cold observable which retries (re-subscribes to) the source observable on error up to the 29 | /// specified number of times or until it successfully terminates. 30 | /// 31 | [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")] 32 | public static IObservable RetryWithBackoffStrategy( 33 | this IObservable source, 34 | int retryCount = 3, 35 | Func strategy = null, 36 | Func retryOnError = null, 37 | IScheduler scheduler = null) 38 | { 39 | strategy = strategy ?? ExponentialBackoff; 40 | scheduler = scheduler ?? Scheduler.Default; 41 | 42 | if (retryOnError == null) 43 | retryOnError = e => true; 44 | 45 | int attempt = 0; 46 | 47 | return Observable.Defer(() => 48 | { 49 | return ((++attempt == 1) ? source : source.DelaySubscription(strategy(attempt - 1), scheduler)) 50 | .Select(item => new Tuple(true, item, null)) 51 | .Catch, Exception>(e => retryOnError(e) 52 | ? Observable.Throw>(e) 53 | : Observable.Return(new Tuple(false, default(T), e))); 54 | }) 55 | .Retry(retryCount) 56 | .SelectMany(t => t.Item1 57 | ? Observable.Return(t.Item2) 58 | : Observable.Throw(t.Item3)); 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /Installer.Raspberry.Application/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Installer.Raspberry.Application/App.xaml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /Installer.Raspberry.Application/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Windows; 4 | using ManagedWimLib; 5 | 6 | namespace Installer.Raspberry.Application 7 | { 8 | /// 9 | /// Interaction logic for App.xaml 10 | /// 11 | public partial class App 12 | { 13 | protected override void OnStartup(StartupEventArgs e) 14 | { 15 | base.OnStartup(e); 16 | InitWimLib(); 17 | } 18 | 19 | private static void InitWimLib() 20 | { 21 | if (Environment.Is64BitProcess) 22 | { 23 | Wim.GlobalInit(Path.Combine("x64", "libwim-15.dll")); 24 | } 25 | else 26 | { 27 | Wim.GlobalInit(Path.Combine("x86", "libwim-15.dll")); 28 | } 29 | } 30 | 31 | protected override void OnExit(ExitEventArgs e) 32 | { 33 | Wim.GlobalCleanup(); 34 | base.OnExit(e); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Installer.Raspberry.Application/Assets/appicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WOA-Project/WoA-Installer-Rpi/94c2ccf783f7e8b7c3a4191d52f580e3711cc82c/Installer.Raspberry.Application/Assets/appicon.ico -------------------------------------------------------------------------------- /Installer.Raspberry.Application/Assets/appicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WOA-Project/WoA-Installer-Rpi/94c2ccf783f7e8b7c3a4191d52f580e3711cc82c/Installer.Raspberry.Application/Assets/appicon.png -------------------------------------------------------------------------------- /Installer.Raspberry.Application/Assets/disabledual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WOA-Project/WoA-Installer-Rpi/94c2ccf783f7e8b7c3a4191d52f580e3711cc82c/Installer.Raspberry.Application/Assets/disabledual.png -------------------------------------------------------------------------------- /Installer.Raspberry.Application/Assets/drive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WOA-Project/WoA-Installer-Rpi/94c2ccf783f7e8b7c3a4191d52f580e3711cc82c/Installer.Raspberry.Application/Assets/drive.png -------------------------------------------------------------------------------- /Installer.Raspberry.Application/Assets/dual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WOA-Project/WoA-Installer-Rpi/94c2ccf783f7e8b7c3a4191d52f580e3711cc82c/Installer.Raspberry.Application/Assets/dual.png -------------------------------------------------------------------------------- /Installer.Raspberry.Application/Assets/full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WOA-Project/WoA-Installer-Rpi/94c2ccf783f7e8b7c3a4191d52f580e3711cc82c/Installer.Raspberry.Application/Assets/full.png -------------------------------------------------------------------------------- /Installer.Raspberry.Application/Assets/gears.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WOA-Project/WoA-Installer-Rpi/94c2ccf783f7e8b7c3a4191d52f580e3711cc82c/Installer.Raspberry.Application/Assets/gears.png -------------------------------------------------------------------------------- /Installer.Raspberry.Application/Assets/heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WOA-Project/WoA-Installer-Rpi/94c2ccf783f7e8b7c3a4191d52f580e3711cc82c/Installer.Raspberry.Application/Assets/heart.png -------------------------------------------------------------------------------- /Installer.Raspberry.Application/Assets/inject-drivers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WOA-Project/WoA-Installer-Rpi/94c2ccf783f7e8b7c3a4191d52f580e3711cc82c/Installer.Raspberry.Application/Assets/inject-drivers.png -------------------------------------------------------------------------------- /Installer.Raspberry.Application/Assets/refresh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WOA-Project/WoA-Installer-Rpi/94c2ccf783f7e8b7c3a4191d52f580e3711cc82c/Installer.Raspberry.Application/Assets/refresh.png -------------------------------------------------------------------------------- /Installer.Raspberry.Application/Assets/windowicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WOA-Project/WoA-Installer-Rpi/94c2ccf783f7e8b7c3a4191d52f580e3711cc82c/Installer.Raspberry.Application/Assets/windowicon.png -------------------------------------------------------------------------------- /Installer.Raspberry.Application/Assets/windows-only.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WOA-Project/WoA-Installer-Rpi/94c2ccf783f7e8b7c3a4191d52f580e3711cc82c/Installer.Raspberry.Application/Assets/windows-only.png -------------------------------------------------------------------------------- /Installer.Raspberry.Application/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Resources; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | using System.Windows; 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [assembly: AssemblyTitle("Installer.Wpf.Raspberry")] 11 | [assembly: AssemblyDescription("")] 12 | [assembly: AssemblyConfiguration("")] 13 | [assembly: AssemblyCompany("")] 14 | [assembly: AssemblyProduct("Installer.Wpf.Raspberry")] 15 | [assembly: AssemblyCopyright("Copyright © 2018")] 16 | [assembly: AssemblyTrademark("")] 17 | [assembly: AssemblyCulture("")] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [assembly: ComVisible(false)] 23 | 24 | //In order to begin building localizable applications, set 25 | //CultureYouAreCodingWith in your .csproj file 26 | //inside a . For example, if you are using US english 27 | //in your source files, set the to en-US. Then uncomment 28 | //the NeutralResourceLanguage attribute below. Update the "en-US" in 29 | //the line below to match the UICulture setting in the project file. 30 | 31 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] 32 | 33 | 34 | [assembly: ThemeInfo( 35 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 36 | //(used if a resource is not found in the page, 37 | // or application resource dictionaries) 38 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 39 | //(used if a resource is not found in the page, 40 | // app, or any theme specific resource dictionaries) 41 | )] 42 | 43 | 44 | // Version information for an assembly consists of the following four values: 45 | // 46 | // Major Version 47 | // Minor Version 48 | // Build Number 49 | // Revision 50 | // 51 | // You can specify all the values or you can default the Build and Revision Numbers 52 | // by using the '*' as shown below: 53 | // [assembly: AssemblyVersion("1.0.*")] 54 | [assembly: AssemblyVersion("1.3.0.1")] 55 | [assembly: AssemblyFileVersion("1.0.0.0")] 56 | -------------------------------------------------------------------------------- /Installer.Raspberry.Application/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Installer.Raspberry.Application.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Installer.Raspberry.Application.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Installer.Raspberry.Application/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /Installer.Raspberry.Application/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Installer.Raspberry.Application { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.7.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | 26 | [global::System.Configuration.UserScopedSettingAttribute()] 27 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 28 | [global::System.Configuration.DefaultSettingValueAttribute("")] 29 | public string WimFolder { 30 | get { 31 | return ((string)(this["WimFolder"])); 32 | } 33 | set { 34 | this["WimFolder"] = value; 35 | } 36 | } 37 | 38 | [global::System.Configuration.UserScopedSettingAttribute()] 39 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 40 | [global::System.Configuration.DefaultSettingValueAttribute("")] 41 | public string DriverPackFolder { 42 | get { 43 | return ((string)(this["DriverPackFolder"])); 44 | } 45 | set { 46 | this["DriverPackFolder"] = value; 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Installer.Raspberry.Application/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Installer.Raspberry.Application/SettingsService.cs: -------------------------------------------------------------------------------- 1 | using Installer.Raspberry.ViewModels; 2 | 3 | namespace Installer.Raspberry.Application 4 | { 5 | public class SettingsService : ISettingsService 6 | { 7 | public string DriverPackFolder 8 | { 9 | get => Settings.Default.DriverPackFolder; 10 | set => Settings.Default.DriverPackFolder = value; 11 | } 12 | 13 | public string WimFolder 14 | { 15 | get => Settings.Default.WimFolder; 16 | set => Settings.Default.WimFolder = value; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /Installer.Raspberry.Application/Views/CompositionRoot.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Installer.Core; 3 | using Installer.Core.FullFx; 4 | using Installer.Core.Services; 5 | using Installer.Raspberry.Application.Views.Parts; 6 | using Installer.Raspberry.Core; 7 | using Installer.Raspberry.ViewModels; 8 | using Installer.UI; 9 | using Installer.ViewModels.Core; 10 | using Installer.Wpf.Core.Services; 11 | using MahApps.Metro.Controls.Dialogs; 12 | using Serilog.Events; 13 | 14 | namespace Installer.Raspberry.Application.Views 15 | { 16 | public static class CompositionRoot 17 | { 18 | public static object GetMainViewModel(IObservable logEvents) 19 | { 20 | ServiceFactory.Current = new DefaultServiceFactory(); 21 | 22 | var deployer = new RaspberryPiDeployer(new ImageFlasher(), new RaspberryPiWindowsDeployer(ServiceFactory.Current.ImageService, new DeploymentPaths(@"Files"))); 23 | var viewService = new ViewService(); 24 | var uiServices = new UIServices(new FilePicker(), viewService, new DialogService(DialogCoordinator.Instance)); 25 | viewService.Register("MarkdownViewer", typeof(MarkdownViewerWindow)); 26 | return new MainViewModel(logEvents, deployer, new PackageImporterFactory(), ServiceFactory.Current.DiskService, uiServices, new SettingsService()); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /Installer.Raspberry.Application/Views/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  18 | 19 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /Installer.Raspberry.Application/Views/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog; 3 | using Serilog.Events; 4 | 5 | namespace Installer.Raspberry.Application.Views 6 | { 7 | public partial class MainWindow 8 | { 9 | public MainWindow() 10 | { 11 | InitializeComponent(); 12 | 13 | IObservable logEvents = null; 14 | 15 | Log.Logger = new LoggerConfiguration() 16 | .MinimumLevel.Verbose() 17 | .WriteTo.Observers(x => logEvents = x, LogEventLevel.Information) 18 | .WriteTo.RollingFile(@"Logs\{Date}.txt") 19 | .CreateLogger(); 20 | 21 | DataContext = CompositionRoot.GetMainViewModel(logEvents); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Installer.Raspberry.Application/Views/Parts/LogPart.xaml: -------------------------------------------------------------------------------- 1 |  9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /Installer.Raspberry.Application/Views/Parts/LogPart.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace Installer.Raspberry.Application.Views.Parts 4 | { 5 | /// 6 | /// Interaction logic for LogPart.xaml 7 | /// 8 | public partial class LogPart : UserControl 9 | { 10 | public LogPart() 11 | { 12 | InitializeComponent(); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Installer.Raspberry.Application/Views/Parts/MarkdownViewerWindow.xaml: -------------------------------------------------------------------------------- 1 |  12 | 13 | 14 | 15 | 16 |