diff --git a/OneMoreSetupActions/Actions/CheckBitnessAction.cs b/OneMoreSetupActions/Actions/CheckBitnessAction.cs new file mode 100644 index 0000000000..569957ab6d --- /dev/null +++ b/OneMoreSetupActions/Actions/CheckBitnessAction.cs @@ -0,0 +1,59 @@ +//************************************************************************************************ +// Copyright © 2021 Steven M Cohn. All rights reserved. +//************************************************************************************************ + +namespace OneMoreSetupActions +{ + using System; + using System.Windows.Forms; + + + /// + /// Confirms that the installer bitness matches the OS bitness + /// + internal class CheckBitnessAction : CustomAction + { + private readonly bool x64; + + + public CheckBitnessAction(Logger logger, Stepper stepper, bool x64) + : base(logger, stepper) + { + this.x64 = x64; + } + + + public override int Install() + { + logger.WriteLine(); + logger.WriteLine("CheckBitnessAction.Install ---"); + + var oarc = Environment.Is64BitOperatingSystem ? "x64" : "x86"; + var iarc = Environment.Is64BitProcess ? "x64" : "x86"; + var rarc = x64 ? "x64" : "x86"; + logger.WriteLine($"Installer architecture ({iarc}), OS architecture ({oarc}), requesting ({rarc})"); + + if (Environment.Is64BitOperatingSystem != Environment.Is64BitProcess || + Environment.Is64BitOperatingSystem != x64) + { + var msg = $"Installer architecture ({iarc}) does not match OS ({oarc}) or request ({rarc})"; + logger.WriteLine(msg); + + MessageBox.Show( + $"This is a {iarc} bit installer.\nYou must use the OneMore {oarc} bit installer.", + "Incompatible Installer", + MessageBoxButtons.OK, MessageBoxIcon.Error); + + return FAILURE; + } + + return SUCCESS; + } + + + public override int Uninstall() + { + return SUCCESS; + } + } +} diff --git a/OneMoreSetupActions/Actions/CheckOneNoteAction.cs b/OneMoreSetupActions/Actions/CheckOneNoteAction.cs new file mode 100644 index 0000000000..6b628276f5 --- /dev/null +++ b/OneMoreSetupActions/Actions/CheckOneNoteAction.cs @@ -0,0 +1,441 @@ +//************************************************************************************************ +// Copyright © 2023 Steven M Cohn. All rights reserved. +//************************************************************************************************ + +namespace OneMoreSetupActions +{ + using Microsoft.Win32; + using System; + using System.IO; + using System.Text.RegularExpressions; + + + /// + /// verifies the OneNote configuration to derisk OneMore not activating. + /// + internal class CheckOneNoteAction : CustomAction + { + private string onenoteCLSID; + private string onenoteCurVer; + private string onenoteTypeLib; + + + public CheckOneNoteAction(Logger logger, Stepper stepper) + : base(logger, stepper) + { + } + + + public override int Install() + { + logger.WriteLine(); + logger.WriteLine("CheckOneNoteAction.Install ---"); + + if (VerifyOneNoteApplication() == SUCCESS && + VerifyRootClass("CLSID") == SUCCESS && + VerifyRootClass(@"WOW6432Node\CLSID") == SUCCESS && + VerifyRootClass(@"Software\Classes\CLSID") == SUCCESS && + VerifyCurVer() == SUCCESS && + VerifyTypeLib() == SUCCESS) + { + return SUCCESS; + } + + logger.Indented = false; + return FAILURE; + } + + + private int VerifyOneNoteApplication() + { + logger.WriteLine(nameof(VerifyOneNoteApplication) + "()"); + logger.Indented = true; + + // [HKEY_CLASSES_ROOT\OneNote.Application] + // @= "Application Class" + var path = "OneNote.Application"; + var key = Registry.ClassesRoot.OpenSubKey(path, false); + + if (key == null) + { + logger.WriteLine($"error finding HKCR:\\{path}"); + return FAILURE; + } + + logger.WriteLine($"checking {key}"); + + try + { + // onenoteCLSID + // [HKEY_CLASSES_ROOT\OneNote.Application\CLSID] + // @="{DC67E480-C3CB-49F8-8232-60B0C2056C8E}" + onenoteCLSID = key.GetSubKeyPropertyValue("CLSID", string.Empty); + if (onenoteCLSID == null) + { + return FAILURE; + } + + logger.WriteLine($"found CLSID {onenoteCLSID}"); + + // onenoteCurVer + // [HKEY_CLASSES_ROOT\OneNote.Application\CurVer] + // @="OneNote.Application.15" + onenoteCurVer = key.GetSubKeyPropertyValue("CurVer", string.Empty); + if (onenoteCurVer == null) + { + return FAILURE; + } + + logger.WriteLine($"found CurVer {onenoteCurVer}"); + } + catch (Exception exc) + { + logger.WriteLine($"error reading OneNote.Application info"); + logger.WriteLine(exc); + return FAILURE; + } + finally + { + key.Dispose(); + } + + logger.WriteLine("OK"); + logger.Indented = false; + return SUCCESS; + } + + + private int VerifyRootClass(string root) + { + logger.WriteLine(nameof(VerifyRootClass) + $"({root})"); + logger.Indented = true; + + // [HKEY_CLASSES_ROOT\CLSID\{DC67E480-C3CB-49F8-8232-60B0C2056C8E}] + // @="Application2 Class" + var path = $"{root}\\{onenoteCLSID}"; + var key = root.StartsWith("Software") + ? Registry.LocalMachine.OpenSubKey(path, false) + : Registry.ClassesRoot.OpenSubKey(path, false); + + if (key == null) + { + logger.WriteLine($"error finding HKCR:\\{path}"); + return FAILURE; + } + + logger.WriteLine($"checking {key}"); + + try + { + // [HKEY_CLASSES_ROOT\CLSID\{DC67E480-C3CB-49F8-8232-60B0C2056C8E}\InprocServer32] + // Assembly="Microsoft.Office.Interop.OneNote, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71E9BCE111E9429C" + var p = "InprocServer32"; + var aname = key.GetSubKeyPropertyValue(p, "Assembly"); + if (aname == null) + { + return FAILURE; + } + + if (VerifyGacAssembly(aname) != SUCCESS) + { + logger.WriteLine($"error confirming {p} '{aname}'"); + return FAILURE; + } + + logger.WriteLine($"verified {p} {aname}"); + + // remaining properties are WARNINGs/Informational... + + //[HKEY_CLASSES_ROOT\CLSID\{DC67E480-C3CB-49F8-8232-60B0C2056C8E}\ProgID] + //@="OneNote.Application.15" + p = "ProgID"; + var progID = key.GetSubKeyPropertyValue(p, string.Empty, false); + if (progID == null) + { + logger.WriteLine($"warning finding subkey {key.Name}\\{p}"); + } + else if (progID != onenoteCurVer) + { + logger.WriteLine($"warning confirming ProgID '{progID}' as {onenoteCurVer}"); + } + else + { + logger.WriteLine($"verified ProgID as {progID}"); + } + + //[HKEY_CLASSES_ROOT\CLSID\{DC67E480-C3CB-49F8-8232-60B0C2056C8E}\TypeLib] + //@="{0EA692EE-BB50-4E3C-AEF0-356D91732725}" + p = "TypeLib"; + var typelib = key.GetSubKeyPropertyValue(p, string.Empty, false); + if (progID == null) + { + logger.WriteLine($"warning finding subkey {key.Name}\\{p}"); + } + else if (!root.StartsWith("Software")) + { + onenoteTypeLib = typelib; + logger.WriteLine($"found TypeLib as {onenoteTypeLib}"); + } + else if (typelib != onenoteTypeLib) + { + logger.WriteLine($"warning confirming TypeLib '{typelib}' as {onenoteTypeLib}"); + } + else + { + logger.WriteLine($"verified TypeLib as {onenoteTypeLib}"); + } + + //[HKEY_CLASSES_ROOT\CLSID\{DC67E480-C3CB-49F8-8232-60B0C2056C8E}\VersionIndependentProgID] + //@="OneNote.Application" + p = "VersionIndependentProgID"; + var vip = key.GetSubKeyPropertyValue(p, string.Empty, false); + if (string.IsNullOrWhiteSpace(vip)) + { + logger.WriteLine($"warning finding subkey {key.Name}\\{p}"); + } + else + { + var path2 = $"{vip}\\CurVer"; + using (var key2 = Registry.ClassesRoot.OpenSubKey(path2, false)) + { + if (key2 == null) + { + logger.WriteLine($"warning finding key {path2}"); + } + else + { + var ver = (string)key2.GetValue(string.Empty); + if (string.IsNullOrWhiteSpace(ver) || + ver != onenoteCurVer) + { + logger.WriteLine($"warning confirming VersionIndependentProgID ({ver ?? "null"} != {onenoteCurVer})"); + } + else + { + logger.WriteLine($"verified VersionIndependentProgID as {ver}"); + } + } + } + } + } + catch (Exception exc) + { + logger.WriteLine($"error reading CLSID info"); + logger.WriteLine(exc); + return FAILURE; + } + finally + { + key.Dispose(); + } + + logger.WriteLine("OK"); + logger.Indented = false; + return SUCCESS; + } + + + private int VerifyGacAssembly(string fullName) + { + // AssemblyName class doesn't expose token so just parse the string + var matches = Regex.Matches(fullName, @"([^,].*), Version=([^,].*), .+?PublicKeyToken=(.*)"); + if (matches.Count != 1 || !matches[0].Success) + { + logger.WriteLine($"error parsing GAC assembly name {fullName}"); + return FAILURE; + } + + var name = matches[0].Groups[1].Value; + var version = matches[0].Groups[2].Value; + var token = matches[0].Groups[3].Value; + + var path = Path.Combine( + Environment.GetEnvironmentVariable("windir"), + @"assembly\\GAC_MSIL", name, $"{version}__{token}", + $"{name}.dll"); + + if (!File.Exists(path)) + { + return FAILURE; + } + + return SUCCESS; + } + + + private int VerifyCurVer() + { + logger.WriteLine(nameof(VerifyCurVer) + "()"); + logger.Indented = true; + + //[HKEY_CLASSES_ROOT\OneNote.Application.15] + //@= "Application2 Class" + var path = onenoteCurVer; + var key = Registry.ClassesRoot.OpenSubKey(path, false); + + if (key == null) + { + logger.WriteLine($"error finding HKCR:\\{path}"); + return FAILURE; + } + + logger.WriteLine($"checking {key}"); + + try + { + //[HKEY_CLASSES_ROOT\OneNote.Application.15\CLSID] + //@= "{DC67E480-C3CB-49F8-8232-60B0C2056C8E}" + var p = "CLSID"; + var clsid = key.GetSubKeyPropertyValue(p, string.Empty); + if (clsid == null) + { + return FAILURE; + } + + if (clsid != onenoteCLSID) + { + logger.WriteLine($"error confirming CLSID '{clsid}' as {onenoteCLSID}"); + return FAILURE; + } + + logger.WriteLine($"verified CLSID {clsid}"); + } + catch (Exception exc) + { + logger.WriteLine($"error reading CurVer info"); + logger.WriteLine(exc); + return FAILURE; + } + finally + { + key.Dispose(); + } + + logger.WriteLine("OK"); + logger.Indented = false; + return SUCCESS; + } + + + private int VerifyTypeLib() + { + logger.WriteLine(nameof(VerifyTypeLib) + "()"); + logger.Indented = true; + + //[HKEY_CLASSES_ROOT\TypeLib\{0EA692EE-BB50-4E3C-AEF0-356D91732725}] + var path = $"TypeLib\\{onenoteTypeLib}"; + var key = Registry.ClassesRoot.OpenSubKey(path, false); + + if (key == null) + { + logger.WriteLine($"error finding HKCR:\\{path}"); + return FAILURE; + } + + logger.WriteLine($"checking {key}"); + + try + { + string p; + var foundLib = false; + + // 1.0, 1.1, ... + foreach (var subname in key.GetSubKeyNames()) + { + using (var sub = key.OpenSubKey(subname, false)) + { + //[HKEY_CLASSES_ROOT\TypeLib\{0EA692EE-BB50-4E3C-AEF0-356D91732725}\1.0] + //"PrimaryInteropAssemblyName"="Microsoft.Office.Interop.OneNote, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71E9BCE111E9429C" + // => C:\Windows\assembly\GAC_MSIL\Microsoft.Office.Interop.OneNote\15.0.0.0__71e9bce111e9429c + var pia = (string)sub.GetValue("PrimaryInteropAssemblyName"); + if (!string.IsNullOrWhiteSpace(pia)) + { + var status = VerifyGacAssembly(pia); + if (status == FAILURE) + { + logger.WriteLine($"error finding GAC assembly {pia}"); + } + else + { + logger.WriteLine($"verified {pia}"); + } + } + else + { + //[HKEY_CLASSES_ROOT\TypeLib\{0EA692EE-BB50-4E3C-AEF0-356D91732725}\1.1] + //@="Microsoft OneNote 15.0 Object Library" + var value = (string)sub.GetValue(string.Empty); + if (Regex.Match(value, @"Microsoft OneNote .+ (?:Object|Type) Library").Success) + { + //[HKEY_CLASSES_ROOT\TypeLib\{0EA692EE-BB50-4E3C-AEF0-356D91732725}\1.1\0\Win32] + //@="C:\\Program Files\\Microsoft Office\\Root\\Office16\\ONENOTE.EXE\\3" + p = "0\\Win32"; + var win32 = sub.GetSubKeyPropertyValue(p, string.Empty, false); + if (win32 == null) + { + logger.WriteLine($@"warning did not find key HKCR:\\{sub.Name}\{p}"); + } + // strip off trailing "\3" + else if (!File.Exists(Path.GetDirectoryName(win32))) + { + logger.WriteLine($@"warning did not find Win32 path {win32}"); + } + else + { + logger.WriteLine($"verified Win32 Object Library {win32}"); + foundLib = true; + } + + //[HKEY_CLASSES_ROOT\TypeLib\{0EA692EE-BB50-4E3C-AEF0-356D91732725}\1.1\0\win64] + //@="C:\\Program Files\\Microsoft Office\\root\\Office16\\ONENOTE.EXE\\3" + p = "0\\Win32"; + var win64 = sub.GetSubKeyPropertyValue(p, string.Empty, false); + if (win64 == null) + { + logger.WriteLine($@"warning did not find key HKCR:\\{sub.Name}\{p}"); + } + // strip off trailing "\3" + else if (!File.Exists(Path.GetDirectoryName(win64))) + { + logger.WriteLine($@"warning did not find win64 path {win64}"); + } + else + { + logger.WriteLine($"verified win64 Object Library {win64}"); + foundLib = true; + } + } + } + } + } + + // if neither Win32 nor win64 subkeys were found + if (!foundLib) + { + logger.WriteLine($"error finding object library info under {path}"); + return FAILURE; + } + } + catch (Exception exc) + { + logger.WriteLine($"error reading CurVer info"); + logger.WriteLine(exc); + return FAILURE; + } + finally + { + key.Dispose(); + } + + logger.WriteLine("OK"); + logger.Indented = false; + return SUCCESS; + } + + + public override int Uninstall() + { + return SUCCESS; + } + } +} diff --git a/OneMoreSetupActions/Logger.cs b/OneMoreSetupActions/Logger.cs index e68fe4fbf2..19ea84a27a 100644 --- a/OneMoreSetupActions/Logger.cs +++ b/OneMoreSetupActions/Logger.cs @@ -17,9 +17,8 @@ internal class Logger : IDisposable public Logger(string name) { - writer = new StreamWriter( - Path.Combine(Path.GetTempPath(), $"{name}.log"), - true); + LogPath = Path.Combine(Path.GetTempPath(), $"{name}.log"); + writer = new StreamWriter(LogPath, true); } @@ -51,6 +50,12 @@ protected virtual void Dispose(bool disposing) #endregion Lifecycle + public string LogPath { get; private set; } + + + public bool Indented { get; set; } + + public void WriteLine() { WriteLine(string.Empty); @@ -59,8 +64,9 @@ public void WriteLine() public void WriteLine(string message) { - Console.WriteLine(message); - writer.WriteLine(message); + var m = Indented ? $"... {message}" : message; + Console.WriteLine(m); + writer.WriteLine(m); writer.Flush(); } diff --git a/OneMoreSetupActions/OneMoreSetupActions.csproj b/OneMoreSetupActions/OneMoreSetupActions.csproj index 31764154cd..820e57b084 100644 --- a/OneMoreSetupActions/OneMoreSetupActions.csproj +++ b/OneMoreSetupActions/OneMoreSetupActions.csproj @@ -43,6 +43,7 @@ + @@ -52,6 +53,8 @@ + + diff --git a/OneMoreSetupActions/Program.cs b/OneMoreSetupActions/Program.cs index 8edddd1736..aadac0c3d3 100644 --- a/OneMoreSetupActions/Program.cs +++ b/OneMoreSetupActions/Program.cs @@ -3,6 +3,7 @@ //************************************************************************************************ #pragma warning disable S1118 // Utility classes should not have public constructors +#pragma warning disable S6605 // "Exists" method should be used instead of the "Any" extension namespace OneMoreSetupActions { @@ -11,6 +12,7 @@ namespace OneMoreSetupActions using System.Linq; using System.Runtime.InteropServices; using System.Security.Principal; + using System.Windows.Forms; class Program @@ -52,14 +54,31 @@ static void Main(string[] args) logger.WriteLine($"direct action: {args[0]} .. {DateTime.Now}"); } + ReportContext(); + + int status; + if (args.Any(a => a == "--x64" || a == "--x86")) { - CheckBitness(args.Any(a => a == "--x64")); + var x64 = args.Any(a => a == "--x64"); + status = new CheckBitnessAction(logger, stepper, x64).Install(); + if (status != CustomAction.SUCCESS) + { + Environment.Exit(status); + } } - ReportContext(); + status = new CheckOneNoteAction(logger, stepper).Install(); + if (status != CustomAction.SUCCESS) + { + MessageBox.Show($"The OneNote installation looks to be invalid. OneMore may not appear " + + "in the OneNote ribbon until OneNote is repaired. For more information, " + + $"check the logs at\n{logger.LogPath}", + "OneNote Configuration Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning); - int status; + // treat as warning for now... + //Environment.Exit(status); + } switch (args[0]) { @@ -77,6 +96,14 @@ static void Main(string[] args) status = new ActiveSetupAction(logger, stepper).Install(); break; + case "--install-checkbitness": + status = new CheckBitnessAction(logger, stepper, true).Install(); + break; + + case "--install-checkonenote": + status = new CheckOneNoteAction(logger, stepper).Install(); + break; + case "--install-edge": status = new EdgeWebViewAction(logger, stepper).Install(); break; @@ -119,22 +146,6 @@ static void Main(string[] args) } - static void CheckBitness(bool x64) - { - var oarc = Environment.Is64BitOperatingSystem ? "x64" : "x86"; - var iarc = Environment.Is64BitProcess ? "x64" : "x86"; - var rarc = x64 ? "x64" : "x86"; - logger.WriteLine($"Installer architecture ({iarc}), OS architecture ({oarc}), requesting ({rarc})"); - - if (Environment.Is64BitOperatingSystem != Environment.Is64BitProcess || - Environment.Is64BitOperatingSystem != x64) - { - logger.WriteLine($"Installer architecture ({iarc}) does not match OS ({oarc}) or request ({rarc})"); - Environment.Exit(CustomAction.USEREXIT); - } - } - - static void ReportContext() { // current user... @@ -188,6 +199,10 @@ static int Install() { logger.WriteLine("install failed; error registering"); logger.WriteLine(exc); + + MessageBox.Show($"Error installing. Check the logs {logger.LogPath}", + "Action Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + return CustomAction.FAILURE; } } @@ -223,6 +238,10 @@ static int Uninstall() { logger.WriteLine("uninstall failed; error unregistering"); logger.WriteLine(exc); + + MessageBox.Show($"Error uninstalling. Check the logs {logger.LogPath}", + "Action Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + return CustomAction.FAILURE; } } diff --git a/OneMoreSetupActions/RegistryHelper.cs b/OneMoreSetupActions/RegistryHelper.cs index cdc09d2de1..dcde253e60 100644 --- a/OneMoreSetupActions/RegistryHelper.cs +++ b/OneMoreSetupActions/RegistryHelper.cs @@ -2,11 +2,14 @@ // Copyright © 2021 Steven M Cohn. All rights reserved. //************************************************************************************************ +#pragma warning disable S6605 // "Exists" method should be used instead of the "Any" extension + namespace OneMoreSetupActions { using Microsoft.Win32; using System; using System.IO; + using System.Linq; using System.Runtime.InteropServices; using System.Security.AccessControl; using System.Security.Principal; @@ -100,6 +103,47 @@ private static void CopyTree(RegistryKey source, RegistryKey target) } + /// + /// Safely return the value of a property under the named subkey. + /// + /// + /// + /// + /// + public static string GetSubKeyPropertyValue( + this RegistryKey key, string subkeyName, string propName, bool verbose = true) + { + try + { + using (var sub = key.OpenSubKey(subkeyName, false)) + { + if (sub == null) + { + if (verbose) + { + logger?.WriteLine($"could not find subkey {key.Name}\\{subkeyName}"); + } + return null; + } + + var value = (string)sub.GetValue(propName); + if (value == null && verbose) + { + logger?.WriteLine($"could not find property {key.Name}\\{subkeyName}\\{propName}"); + } + + return value; + } + } + catch (Exception exc) + { + logger?.WriteLine($"error reading subkey property {key.Name}\\{subkeyName}\\{propName}"); + logger?.WriteLine(exc); + return null; + } + } + + /// /// Returns the SID of the currently logged in user that is running the installer, /// which may differ from the current user context because the installer may be diff --git a/build.ps1 b/build.ps1 index c9902ed284..7a42bb527f 100644 --- a/build.ps1 +++ b/build.ps1 @@ -20,189 +20,193 @@ or reinstalling Visual Studio. It is required to build installer kits from the c # CmdletBinding adds -Verbose functionality, SupportsShouldProcess adds -WhatIf [CmdletBinding(SupportsShouldProcess = $true)] param ( - [int] $configbits = 64, - [switch] $fast, - [switch] $both, - [switch] $prep - ) + [int] $configbits = 64, + [switch] $fast, + [switch] $both, + [switch] $prep + ) Begin { - $script:devenv = '' - $script:vdproj = '' - - function FindVisualStudio - { - $cmd = Get-Command devenv -ErrorAction:SilentlyContinue - if ($cmd -ne $null) - { - $script:devenv = $cmd.Source - return $true - } - - $0 = 'C:\Program Files\Microsoft Visual Studio\2022' - if (FindVS $0) { return $true } - - $0 = 'C:\Program Files (x86)\Microsoft Visual Studio\2019' - return FindVS $0 - } - - function FindVS - { - param($vsroot) - $script:devenv = Join-Path $vsroot 'Enterprise\Common7\IDE\devenv.com' - - if (!(Test-Path $devenv)) - { - $script:devenv = Join-Path $vsroot 'Professional\Common7\IDE\devenv.com' - } - if (!(Test-Path $devenv)) - { - $script:devenv = Join-Path $vsroot 'Community\Common7\IDE\devenv.com' - } - - if (!(Test-Path $devenv)) - { - Write-Host "devenv not found in $vsroot" -ForegroundColor Yellow - return $false - } - - $script:ideroot = Split-Path -Parent $devenv - return $true - } - - function DisableOutOfProcBuild - { - $0 = Join-Path $ideroot 'CommonExtensions\Microsoft\VSI\DisableOutOfProcBuild' - if (Test-Path $0) - { - Push-Location $0 - if (Test-Path .\DisableOutOfProcBuild.exe) { - .\DisableOutOfProcBuild.exe - } - Pop-Location - Write-Host '... disabled out-of-proc builds; reboot is recommended' - return - } - Write-Host "*** could not find $0\DisableOutOfProcBuild.exe" -ForegroundColor Yellow - } - - function PreserveVdproj - { - Write-Host '... preserving vdproj' -ForegroundColor DarkGray - Copy-Item $vdproj .\vdproj.tmp -Force -Confirm:$false - } - - function RestoreVdproj - { - Write-Host '... restoring vdproj' -ForegroundColor DarkGray - Copy-Item .\vdproj.tmp $vdproj -Force -Confirm:$false - Remove-Item .\vdproj.tmp -Force - } - - function Configure - { - param([int]$bitness) - $lines = @(Get-Content $vdproj) - - $productVersion = $lines | ` - Where-Object { $_ -match '"ProductVersion" = "8:(.+?)"' } | ` - ForEach-Object { $matches[1] } - - Write-Host - Write-Host "... configuring vdproj for x$bitness build of $productVersion" -ForegroundColor Yellow - - '' | Out-File $vdproj -nonewline - - $lines | ForEach-Object ` - { - if ($_ -match '"OutputFileName" = "') - { - # "OutputFilename" = "8:Debug\\OneMore_v_Setupx86.msi" - $line = $_.Replace('OneMore_v_', "OneMore_$($productVersion)_") - if ($bitness -eq 64) { - $line.Replace('x86', 'x64') | Out-File $vdproj -Append - } else { - $line.Replace('x64', 'x86') | Out-File $vdproj -Append - } - } - elseif ($_ -match '"DefaultLocation" = "') - { - # "DefaultLocation" = "8:[ProgramFilesFolder][Manufacturer]\\[ProductName]" - if ($bitness -eq 64) { - $_.Replace('ProgramFilesFolder', 'ProgramFiles64Folder') | Out-File $vdproj -Append - } else { - $_.Replace('ProgramFiles64Folder', 'ProgramFilesFolder') | Out-File $vdproj -Append - } - } - elseif ($_ -match '"TargetPlatform" = "') - { - # x86 -> "3:0" - # x64 -> "3:1" - if ($bitness -eq 64) { - '"TargetPlatform" = "3:1"' | Out-File $vdproj -Append - } else { - '"TargetPlatform" = "3:0"' | Out-File $vdproj -Append - } - } - #elseif ($_ -match '"SourcePath" = .*WebView2Loader\.dll"$') - #{ - # if ($bitness -eq 64) { - # $_.Replace('\\x86', '\\x64') | Out-File $vdproj -Append - # $_.Replace('win-x86', 'win-x64') | Out-File $vdproj -Append - # } else { - # $_.Replace('\\x64', '\\x86') | Out-File $vdproj -Append - # $_.Replace('win-x64', 'win-x86') | Out-File $vdproj -Append - # } - #} - elseif (($_ -match '"Name" = "8:OneMoreSetupActions --install ') -or ` - ($_ -match '"Arguments" = "8:--install ')) - { - if ($bitness -eq 64) { - $_.Replace('x86', 'x64') | Out-File $vdproj -Append - } else { - $_.Replace('x64', 'x86') | Out-File $vdproj -Append - } - } - elseif ($_ -notmatch '^"Scc') - { - $_ | Out-File $vdproj -Append - } - } - } - - function BuildFast - { - param([int]$bitness) - Write-Host "... building x$bitness DLLs" -ForegroundColor Yellow - - # build... - - Push-Location OneMore - BuildComponent 'OneMore' $true - Pop-Location - - Push-Location OneMoreCalendar - BuildComponent 'OneMoreCalendar' $true - Pop-Location - - Push-Location OneMoreProtocolHandler - BuildComponent 'OneMoreProtocolHandler' - Pop-Location + $script:devenv = '' + $script:vsregedit = '' + $script:vdproj = '' + + function FindVisualStudio + { + $cmd = Get-Command devenv -ErrorAction:SilentlyContinue + if ($cmd -ne $null) + { + $script:devenv = $cmd.Source + $script:ideroot = Split-Path -Parent $devenv + $script:vsregedit = Join-Path $ideroot 'VSRegEdit.exe' + return $true + } + + $0 = 'C:\Program Files\Microsoft Visual Studio\2022' + if (FindVS $0) { return $true } + + $0 = 'C:\Program Files (x86)\Microsoft Visual Studio\2019' + return FindVS $0 + } + + function FindVS + { + param($vsroot) + $script:devenv = Join-Path $vsroot 'Enterprise\Common7\IDE\devenv.com' + + if (!(Test-Path $devenv)) + { + $script:devenv = Join-Path $vsroot 'Professional\Common7\IDE\devenv.com' + } + if (!(Test-Path $devenv)) + { + $script:devenv = Join-Path $vsroot 'Community\Common7\IDE\devenv.com' + } + + if (!(Test-Path $devenv)) + { + Write-Host "devenv not found in $vsroot" -ForegroundColor Yellow + return $false + } + + $script:ideroot = Split-Path -Parent $devenv + $script:vsregedit = Join-Path $ideroot 'VSRegEdit.exe' + return $true + } + + function DisableOutOfProcBuild + { + $0 = Join-Path $ideroot 'CommonExtensions\Microsoft\VSI\DisableOutOfProcBuild' + if (Test-Path $0) + { + Push-Location $0 + if (Test-Path .\DisableOutOfProcBuild.exe) { + .\DisableOutOfProcBuild.exe + } + Pop-Location + Write-Host '... disabled out-of-proc builds; reboot is recommended' + return + } + Write-Host "*** could not find $0\DisableOutOfProcBuild.exe" -ForegroundColor Yellow + } + + function PreserveVdproj + { + Write-Host '... preserving vdproj' -ForegroundColor DarkGray + Copy-Item $vdproj .\vdproj.tmp -Force -Confirm:$false + } + + function RestoreVdproj + { + Write-Host '... restoring vdproj' -ForegroundColor DarkGray + Copy-Item .\vdproj.tmp $vdproj -Force -Confirm:$false + Remove-Item .\vdproj.tmp -Force + } + + function Configure + { + param([int]$bitness) + $lines = @(Get-Content $vdproj) + + $productVersion = $lines | ` + Where-Object { $_ -match '"ProductVersion" = "8:(.+?)"' } | ` + ForEach-Object { $matches[1] } + + Write-Host + Write-Host "... configuring vdproj for x$bitness build of $productVersion" -ForegroundColor Yellow + + '' | Out-File $vdproj -nonewline + + $lines | ForEach-Object ` + { + if ($_ -match '"OutputFileName" = "') + { + # "OutputFilename" = "8:Debug\\OneMore_v_Setupx86.msi" + $line = $_.Replace('OneMore_v_', "OneMore_$($productVersion)_") + if ($bitness -eq 64) { + $line.Replace('x86', 'x64') | Out-File $vdproj -Append + } else { + $line.Replace('x64', 'x86') | Out-File $vdproj -Append + } + } + elseif ($_ -match '"DefaultLocation" = "') + { + # "DefaultLocation" = "8:[ProgramFilesFolder][Manufacturer]\\[ProductName]" + if ($bitness -eq 64) { + $_.Replace('ProgramFilesFolder', 'ProgramFiles64Folder') | Out-File $vdproj -Append + } else { + $_.Replace('ProgramFiles64Folder', 'ProgramFilesFolder') | Out-File $vdproj -Append + } + } + elseif ($_ -match '"TargetPlatform" = "') + { + # x86 -> "3:0" + # x64 -> "3:1" + if ($bitness -eq 64) { + '"TargetPlatform" = "3:1"' | Out-File $vdproj -Append + } else { + '"TargetPlatform" = "3:0"' | Out-File $vdproj -Append + } + } + #elseif ($_ -match '"SourcePath" = .*WebView2Loader\.dll"$') + #{ + # if ($bitness -eq 64) { + # $_.Replace('\\x86', '\\x64') | Out-File $vdproj -Append + # $_.Replace('win-x86', 'win-x64') | Out-File $vdproj -Append + # } else { + # $_.Replace('\\x64', '\\x86') | Out-File $vdproj -Append + # $_.Replace('win-x64', 'win-x86') | Out-File $vdproj -Append + # } + #} + elseif (($_ -match '"Name" = "8:OneMoreSetupActions --install ') -or ` + ($_ -match '"Arguments" = "8:--install ')) + { + if ($bitness -eq 64) { + $_.Replace('x86', 'x64') | Out-File $vdproj -Append + } else { + $_.Replace('x64', 'x86') | Out-File $vdproj -Append + } + } + elseif ($_ -notmatch '^"Scc') + { + $_ | Out-File $vdproj -Append + } + } + } + + function BuildFast + { + param([int]$bitness) + Write-Host "... building x$bitness DLLs" -ForegroundColor Yellow + + # build... + + Push-Location OneMore + BuildComponent 'OneMore' $true + Pop-Location + + Push-Location OneMoreCalendar + BuildComponent 'OneMoreCalendar' $true + Pop-Location + + Push-Location OneMoreProtocolHandler + BuildComponent 'OneMoreProtocolHandler' + Pop-Location - Push-Location OneMoreSetupActions - BuildComponent 'OneMoreSetupActions' - Pop-Location - } - - function BuildComponent - { - param($name, $restore = $false) - # output file cannot exist before build - if (Test-Path .\Debug\*) - { - Remove-Item .\Debug\*.* -Force -Confirm:$false - } + Push-Location OneMoreSetupActions + BuildComponent 'OneMoreSetupActions' + Pop-Location + } + + function BuildComponent + { + param($name, $restore = $false) + # output file cannot exist before build + if (Test-Path .\Debug\*) + { + Remove-Item .\Debug\*.* -Force -Confirm:$false + } # nuget restore if ($restore) { @@ -210,70 +214,86 @@ Begin write-Host $cmd -ForegroundColor DarkGray nuget restore .\$name.csproj } - $cmd = "$devenv .\$name.csproj /build ""Debug|AnyCPU"" /project $name /projectconfig Debug" - write-Host $cmd -ForegroundColor DarkGray - . $devenv .\$name.csproj /build "Debug|AnyCPU" /project $name /projectconfig Debug - } - - function Build - { - param([int]$bitness) - Write-Host "... building x$bitness MSI" -ForegroundColor Yellow - - # output file cannot exist before build - if (Test-Path .\Debug\*) - { - Remove-Item .\Debug\*.* -Force -Confirm:$false - } - - $cmd = "$devenv .\OneMoreSetup.vdproj /build ""Debug|x$bitness"" /project Setup /projectconfig Debug /out `$env:TEMP\OneMoreBuild.log" - write-Host $cmd -ForegroundColor DarkGray - - # build - . $devenv .\OneMoreSetup.vdproj /build "Debug|x$bitness" /project Setup /projectconfig Debug /out $env:TEMP\OneMorebuild.log - - # move msi to Downloads for safe-keeping and to allow next Platform build - Move-Item .\Debug\*.msi $home\Downloads -Force - Write-Host "... x$bitness MSI copied to $home\Downloads\" -ForegroundColor DarkYellow - } + $cmd = "$devenv .\$name.csproj /build ""Debug|AnyCPU"" /project $name /projectconfig Debug" + write-Host $cmd -ForegroundColor DarkGray + . $devenv .\$name.csproj /build "Debug|AnyCPU" /project $name /projectconfig Debug + } + + function Build + { + param([int]$bitness) + Write-Host "... building x$bitness MSI" -ForegroundColor Yellow + + # output file cannot exist before build + if (Test-Path .\Debug\*) + { + Remove-Item .\Debug\*.* -Force -Confirm:$false + } + + if ($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent) + { + Write-Host '... enabling MSBuild verbose logging' -ForegroundColor DarkYellow + $cmd = ". '$vsregedit' set local HKCU General MSBuildLoggerVerbosity dword 4`n" + write-Host $cmd -ForegroundColor DarkGray + . $vsregedit set local HKCU General MSBuildLoggerVerbosity dword 4 | Out-Null + } + + $cmd = "$devenv .\OneMoreSetup.vdproj /build ""Debug|x$bitness"" /project Setup /projectconfig Debug /out `$env:TEMP\OneMoreBuild.log" + write-Host $cmd -ForegroundColor DarkGray + + # build + . $devenv .\OneMoreSetup.vdproj /build "Debug|x$bitness" /project Setup /projectconfig Debug /out $env:TEMP\OneMorebuild.log + + # move msi to Downloads for safe-keeping and to allow next Platform build + Move-Item .\Debug\*.msi $home\Downloads -Force + Write-Host "... x$bitness MSI copied to $home\Downloads\" -ForegroundColor DarkYellow + + if ($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent) + { + Write-Host '... disabling MSBuild verbose logging' -ForegroundColor DarkYellow + $cmd = ". '$vsregedit' set local HKCU General MSBuildLoggerVerbosity dword 1`n" + write-Host $cmd -ForegroundColor DarkGray + . $vsregedit set local HKCU General MSBuildLoggerVerbosity dword 1 | Out-Null + } + } } Process { - if (-not (FindVisualStudio)) - { - return - } - - if ($prep) - { - DisableOutOfProcBuild - return - } - - if ($fast) - { - BuildFast 64 - } - else - { - Push-Location OneMoreSetup - $script:vdproj = Resolve-Path .\OneMoreSetup.vdproj - PreserveVdproj - - if ($configbits -eq 86 -or $both) - { - Configure 86 - Build 86 - } - - if ($configBits -eq 64 -or $both) - { - Configure 64 - Build 64 - } - - RestoreVdproj - } - - Pop-Location + if (-not (FindVisualStudio)) + { + return + } + + if ($prep) + { + DisableOutOfProcBuild + return + } + + if ($fast) + { + BuildFast 64 + } + else + { + Push-Location OneMoreSetup + $script:vdproj = Resolve-Path .\OneMoreSetup.vdproj + PreserveVdproj + + if ($configbits -eq 86 -or $both) + { + Configure 86 + Build 86 + } + + if ($configBits -eq 64 -or $both) + { + Configure 64 + Build 64 + } + + RestoreVdproj + } + + Pop-Location }